---
question: How do I write an in-engine agent class?
kind: task
via_web: available
via_cli: available
via_python: available
via_api: available
via_config: na
ref_web: Settings › Agents
ref_cli: abi new agent
ref_python: Agent
ref_api: POST /api/agents/
prereq: The CLI and Python steps need an in-engine module listed under `modules:` in `config.yaml`. No module yet? See [Creating a Module](/user-guide/build/modules/creating-a-module).
next:
  - /capabilities/build/agents/give-it-tools
  - /capabilities/build/agents/enable-in-a-workspace
  - /capabilities/build/agents/test-an-agent
---

# Create an agent

An agent binds a chat model to tools (workflows, integrations) and to other agents. In an in-engine module, an agent is a class with a `New()` class method: the name, system prompt, tools and sub-agents are all in one place, and the engine builds it at startup.

A Nexus workspace can also hold agents of its own: a name, a system prompt and a model, stored in the workspace. The Web UI and the REST API create those. They have no tools or sub-agents. For those, write the class with the CLI or Python.

:::tip Remote module?
In a [remote module](/user-guide/build/sdk/build-a-remote-module) an agent is an SDK `IntentAgent` built by an async `create_agent(...)` function and published with an `AgentDescriptor`; `abi new agent` writes that variant when you run it inside a remote module. This page covers the in-engine agent. To choose between the two, see [Creating a Module](/user-guide/build/modules/creating-a-module#remote-or-in-engine).
:::

## Web UI

This creates a workspace agent. It does not write a class.

### Open the agent list

In the workspace, open Settings, then Agents.

### Add the agent

Click **Add Agent**. Fill in **Name** (required), **Icon**, **Description** and **System Prompt**, then click **Add Agent**.

![Workspace Settings on Agents: the Add New Agent form](/abi/Screenshot_Add_Agent.png)

*Settings › Agents › Add Agent: name, icon, description and system prompt.*

### Turn it on

The new agent starts off. Check **Enabled** on its row: see [Enable in a workspace](/user-guide/build/agents/enable-in-a-workspace).

### Set its model

Click the row to open **Edit Agent**, fill in **Model ID** with the provider's own model id (for OpenRouter, `google/gemma-4-26b-a4b-it`), then click **Save**. See [Edit prompt & model](/user-guide/build/agents/edit-prompt-and-model).

:::note Which provider answers
A workspace agent answers through the provider stored on its record (`openrouter`, `openai`, `anthropic`, `mistral`, `google` or `xai`), with that provider's key from Settings › Secrets (`OPENROUTER_API_KEY`, ...). The form and **Save** do not store a provider: set `provider` when you create the agent with the REST API. Without one, the chat falls back to a local Ollama server when one runs.
:::

## CLI

### Scaffold the agent

From the project root:

```bash
abi new agent Reviewer src/my_module/agents
```

It writes `ReviewerAgent.py`. The arguments are `<name> [path]`; the path defaults to the current directory. (Inside a module, `abi new module <name> --in-engine` already scaffolds a first agent.) The command is [`abi new agent`](/user-guide/reference/cli#abi-new-agent).

### Chat with it from a terminal

```bash
abi chat my_module ReviewerAgent    # <module> <agent class name>
```

`<module>` is the module's entry under `modules:`. `abi chat` loads the engine, so in a NATS project it needs the broker (`abi dev up` or `abi stack start`). The full command list is the [CLI reference](/user-guide/reference/cli).

## Python

### Write the class

The scaffold is a subclass of `Agent` (from `naas_abi_core.services.agent.Agent`) with class attributes and a `New(...)` constructor:

```python title="agents/ReviewerAgent.py"
from __future__ import annotations

from typing import Optional

from naas_abi_core.services.agent.Agent import (
    Agent,
    AgentConfiguration,
    AgentSharedState,
)


class ReviewerAgent(Agent):
    name: str = "Reviewer"
    description: str = "Reviews documents and answers questions about them."
    avatar_url: str = "https://example.com/reviewer.png"
    system_prompt: str = """<role>
You are ReviewerAgent, a careful reviewer.
</role>

<objective>
Help the user review their documents.
</objective>

<tools>
[TOOLS]
</tools>

<constraints>
- Only use the provided tools; do not fabricate data.
</constraints>
"""

    @classmethod
    def New(
        cls,
        agent_shared_state: Optional[AgentSharedState] = None,
        agent_configuration: Optional[AgentConfiguration] = None,
    ) -> "ReviewerAgent":
        from naas_abi_core.engine.context import get_default_model_registry

        # The workspace's default chat model, from the model registry.
        registry = get_default_model_registry()
        assert registry is not None, "ModelRegistryService not initialized"
        chat_model = registry.get_default_chat_model()

        tools: list = []   # workflow and integration tools go here
        agents: list = []  # sub-agents this agent can delegate to

        if agent_configuration is None:
            tools_section = "\n".join(f"- {t.name}: {t.description}" for t in tools)
            agent_configuration = AgentConfiguration(
                system_prompt=cls.system_prompt.replace("[TOOLS]", tools_section)
            )
        if agent_shared_state is None:
            agent_shared_state = AgentSharedState(thread_id="0")

        return cls(
            name=cls.name,
            description=cls.description,
            chat_model=chat_model,
            tools=tools,
            agents=agents,
            memory=None,
            state=agent_shared_state,
            configuration=agent_configuration,
        )
```

The model is the registry's default (`services.model_registry.default_chat_model` in `config.yaml`). For a specific model, ask the registry for its canonical id: `registry.get_chat_model("gemma-4-26b-a4b-it")`. A module that needs its own settings reads them from its configuration: `ABIModule.get_instance().configuration`.

The scaffold also has two optional hooks, `onHumanMessage(message)` and `onAImessage(message, agent_name)`, called on every user message and every AI message (also from sub-agents). They are for observation: what they return is ignored, an exception in them is logged and swallowed, and they run inline, so keep them quick.

### Let the engine load it

The engine imports every `.py` file directly under the module's `agents/` folder (not its subfolders, and not `*test.py`) and calls `New()` on each agent class at startup. A module-level `create_agent()` function in the file is used as `New`. No registration is needed. Make sure the module is listed under `modules:` in `config.yaml`.

:::warning Import inside New()
The engine takes every `Expose` class it finds in an `agents/` file for an agent, imported ones included, and calls its `New()`. Import workflows and pipelines inside `New()`, as below, not at the top of the file.
:::

## REST API

There is no HTTP call that writes the agent class. The Nexus API creates a workspace agent, and the engine API calls an agent it has loaded.

### Create a workspace agent

```http
POST /api/agents/
```

Body: `workspace_id` and `name`, plus optional `description`, `system_prompt`, `model_id`, `provider`, `logo_url` and `enabled` (off by default). Auth: a Nexus user session with access to the workspace.

### Turn it on

```http
PATCH /api/agents/{id}
```

Send `enabled: true`. Its prompt and model are on [Edit prompt & model](/user-guide/build/agents/edit-prompt-and-model).

### Call an existing agent

Every agent the engine loads is exposed at:

```http
POST /agents/{agent_name}/completion
POST /agents/{agent_name}/stream-completion
```

`{agent_name}` is the agent's `name`, with characters other than letters, digits, `_` and `-` replaced by `_` (`Multi Models` becomes `Multi_Models`).

```json title="Request body"
{
  "prompt": "Your question here",
  "thread_id": 1
}
```

Auth: `Authorization: Bearer` with `ABI_API_KEY`. The full process list is the [API](/api).

Agents published by [remote modules](/user-guide/build/sdk/build-a-remote-module) have no such route: reach them through Nexus or the chat API (`POST /api/chat/stream`, selecting the agent by its id). See the [REST API](/user-guide/build/applications/api).

## Config

Not applicable. `config.yaml` does not define the agent class: it lists the module under `modules:`, and [which agents a workspace shows](/user-guide/build/agents/enable-in-a-workspace?via=config).

## Routing between agents with `IntentAgent`

`IntentAgent` (from `naas_abi_core.services.agent.IntentAgent`) extends `Agent` with intent mapping: it matches the user's message to intents with an embedding model and routes to the tool or sub-agent the intent names. Most marketplace agents are `IntentAgent`s. They add an `embedding_model` and a list of `Intent` objects:

```python
from naas_abi_core.services.agent.IntentAgent import IntentAgent
from naas_abi_core.services.agent.beta.IntentMapper import Intent, IntentScope, IntentType

INTENTS = [
    Intent(
        intent_value="review a document",
        intent_type=IntentType.TOOL,        # TOOL, AGENT or RAW
        intent_target="review_document",    # the tool or agent name
        intent_scope=IntentScope.DIRECT,    # DIRECT (default is ALL)
    ),
]

# inside New(): embedding_model = registry.get_default_embedding_model().model
return cls(
    name=cls.name,
    description=cls.description,
    chat_model=chat_model,
    embedding_model=embedding_model,
    tools=tools,
    agents=agents,
    intents=INTENTS,
    memory=None,
    state=agent_shared_state,
    configuration=agent_configuration,
)
```

For a complete working example, read an agent of the marketplace, such as `naas_abi_marketplace/applications/github/agents/GitHubAgent.py`.

## Adding tools from workflows

Workflows expose themselves as tools with `as_tools()`. Instantiate one with its configuration and add its tools:

```python
# ... inside New():
from my_module.workflows.MyWorkflow import MyWorkflow, MyWorkflowConfiguration

workflow = MyWorkflow(MyWorkflowConfiguration())
tools += workflow.as_tools()
```

See [Creating a Workflow](/user-guide/build/agents/creating-a-workflow).

## Adding tools from integrations

By convention an integration module exposes a module-level `as_tools(configuration)` function that returns LangChain tools:

```python
# ... inside New():
from my_module import ABIModule
from my_module.integrations.MyIntegration import MyIntegrationConfiguration, as_tools

api_key = ABIModule.get_instance().configuration.my_api_key
if api_key:
    tools += as_tools(MyIntegrationConfiguration(api_key=api_key))
```

See [Creating an Integration](/user-guide/build/agents/creating-an-integration).

## Using an agent as a tool in another agent

Pass agents in the `agents=[]` list:

```python
# ... inside New():
from naas_abi.agents.OntologyEngineerAgent import OntologyEngineerAgent

ontology_agent = OntologyEngineerAgent.New()

return cls(
    name=cls.name,
    ...
    agents=[ontology_agent],   # this agent can now delegate to OntologyEngineerAgent
)
```

## Persistent memory

Memory is automatic for agents built with `memory=None`. Inside an Engine:

- In a project without NATS, `POSTGRES_URL` wins when it is set, as in 2.x.
- Otherwise, when the Engine loads the Document Service (`services.document` in `config.yaml`; always in NATS mode), conversations are kept there. That needs `naas-abi-core[nats]`. History then survives restarts and appears in the Nexus System app under the `naas_abi_core.services.agent` namespace.

Outside an Engine (scripts, tests), set `POSTGRES_URL` for persistent memory:

```bash
POSTGRES_URL=postgresql://abi_user:abi_password@localhost:5432/abi_memory
```

Without either, the agent falls back to in-memory storage (history lost on restart).
Memory written by earlier releases to `POSTGRES_URL` moves over with
`abi agent migrate-memory` (a dry run until you add `--apply`).
