> Source: https://wexa.ai/docs/agents/create-agent

# create-agent

[Can be held for approval](/docs/sdks-and-apis/how-every-tool-call-works#lifecycle-and-governance)

Create an agent in the project's agent registry and get back its `agent_id`, which `get-agent`, `update-agent`, `delete-agent` and `run-agent` accept unchanged. For a multi-agent process flow use [create-process-flow](/docs/automation/create-process-flow). Needs the `fabric:orchestrate.write` grant.

**Create an agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"create-agent","arguments":{"name":"Support assistant","role":"Conversational","has_knowledge_base":true,"context":["support_faq"]}}}'
```

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000207\",\"result\":{\"agent_id\":\"6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48\"}}"
    }
  ],
  "isError": false
}
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/create-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Support assistant","role":"Conversational","has_knowledge_base":true,"context":["support_faq"]}'
```

```json
{
  "lifecycle_id": "qlc_000207",
  "result": {
    "agent_id": "6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48"
  }
}
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.createAgent({
  name: 'Support assistant',
  role: 'Conversational',
  has_knowledge_base: true,
  context: ['support_faq']
})
```

```ts
// result is the payload itself, with no `.result` to read
{
  agent_id: '6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48'
}
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.create_agent(
    name="Support assistant",
    role="Conversational",
    has_knowledge_base=True,
    context=["support_faq"],
)
```

```python
# out is the result, already unwrapped
{
    "agent_id": "6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48"
}
```

## Parameters

| Parameter | Type | Required | Notes |
| --- | --- | --- | --- |
| `context` | array | Optional | Knowledge-base context tags. |
| `description` | string | Optional | What the agent is for. |
| `has_knowledge_base` | boolean | Optional | Set `true` with at least one tag in `context` so a Conversational or Voice agent can run without a skill. |
| `model` | string | Optional | Model id from `list-models` `settable`, or `"auto"`, which picks a model per call (see More details). Omitted, the agent is stored with the organization's default model from the AI Models registry, or the first registry model when none is marked default. `system_model` is refused, and so is an id not in the organization's AI Models registry. |
| `name` | string | Required | Agent name. Non-empty. |
| `prompt` | string | Optional | System prompt template. |
| `role` | string | Optional | The agent type: Conversational (default), Task, Voice, Proxy or Interop. |
| `skill_ids` | array | Optional | Skill ids from [list-skills](/docs/connectors/list-skills). Skills in this project. |
| `type` | string | Optional | Node kind. `create-agent` makes a `skilled_agent` (the default); the other kinds are declared in a `create-process-flow` manifest; a node kind other than `skilled_agent` is refused. |

**Errors**

- `ValidationError` (400): the organization agent limit is full. The message is `Customer has exceeded number of coworkers`. See [the organization agent limit](/docs/administration/usage-quota-and-billing#limits-and-behavior).
- `ValidationError` (422): no `model` was named and the AI Models registry is empty. Call `list-models` and pass an id from `settable`.

**Show full response**

A real response, captured from a live call to `create-agent`:

**Result of create-agent**

**REST API**

```json
{
  "lifecycle_id": "qlc_000207",
  "result": {
    "agent_id": "6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48"
  }
}
```

**Wexa MCP server**

```json
{
  "content": [
    {
      "type": "text",
      "text": "{\"lifecycle_id\":\"qlc_000207\",\"result\":{\"agent_id\":\"6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48\"}}"
    }
  ],
  "isError": false
}
```

**TypeScript SDK**

```ts
// the object you get back IS the result — there is no `.result` to read
{
  agent_id: '6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48'
}

// The TS SDK names these camelCase — lifecycleId, traceId — where Python uses
// lifecycle_id and trace_id. Both are non-enumerable: readable, but omitted by
// Object.keys() and JSON.stringify(). `result.lifecycleId` here is undefined.
result.lifecycleId // 'qlc_000207'
```

**Python SDK**

```python
# a dict subclass; `result` is already unwrapped
{
 "agent_id": "6ab196817566f41b5c28e335.6ab19681c73ec18cad7c9c48"
}

out.lifecycle_id  # 'qlc_000207' — an attribute, not a key
```

**More details**

`"auto"` has Wexa pick, on every call, the cheapest permitted model that is good enough for the step; routing rules still apply.

### What an agent is made of

An agent is one LLM worker definition: a single set of instructions, tools and model configuration. It is not a container for other agents and not a small [process flow](/docs/automation/create-process-flow). Three parts change at different rates, which is why an agent stays reviewable.

| Part | It says | Where it lives |
| --- | --- | --- |
| Instructions | What the agent is for and how it behaves | A prompt template |
| Tools | What it may do: the Wexa tools it can call and the skills it holds | A `skills` list of real skill ids |
| Model | Which LLM it runs on, one the platform provides or one you bring | An `llm` block whose `model` names it |

Two more settings belong to the definition, not to a single run. An agent that does work can pause and wait for a person before it proceeds. Any agent can receive a summary of the agents that ran before it, and be told which ones. Both are off unless you turn them on.

In the console, open **Orchestrate**, then **Agents**, and choose **New agent**. The wizard offers the types listed under [agent types](#agent-types). The old `system_model` value is refused for a new agent. See [list-models](/docs/agents/list-models) for how a model is chosen.

### Choose an agent or a process flow

Choose by the shape of the work, not its size. Use one agent when the work is one worker's job: one set of instructions, one role, one model, however many steps it takes inside. Use a [process flow](/docs/automation/create-process-flow) when the work needs more than one worker, that is, two parts want different instructions or skills, or a decision routes between them. A long single-role task is still one agent. Two short tasks that need different instructions are already a process flow.

To give the agent something to reach and a way to start without a person, see [Skills, actions and triggers](/docs/automation/overview).

### Agent fields

An agent has the same fields whether you create it alone or as a node in a [create-process-flow](/docs/automation/create-process-flow) manifest. `type` is the node kind and `role` is the badge the console shows, on both.

| What it is | `create-agent` argument | Manifest agent field |
| --- | --- | --- |
| Name | `name` | `title` |
| Node kind | `type` (only `skilled_agent`) | `type`: `skilled_agent`, `decider_agent` or `interop_agent` |
| Role badge | `role` | `role` |
| What the agent is for | `description` | `role_description` |
| Task prompt | `prompt` (string) | `prompt.template` |
| Skills | `skill_ids` | `skills` |
| Knowledge base | `has_knowledge_base`, `context` | `has_knowledge_base`, `context` |
| Model | `model` | `llm.model` |

A manifest node also takes wiring and access fields that only make sense inside a process flow, such as `ref`, `next` and `context_access`.

### Running without a skill

An agent with no skill runs only when it can answer without one.

| Type | Runs with no skill when |
| --- | --- |
| Conversational | it has `has_knowledge_base: true` and at least one tag in `context` |
| Voice | the same as Conversational |
| Task | never: give it a skill |
| Proxy | never: give it a skill |
| Interop (made with `create-agent`) | never: give it a skill |

The rule reads the stored role. If you send a `role` that is not `Conversational` or `Voice`, the agent needs a skill whatever its `type` is.

Two more things let an agent with no skill run. Long-term memory does it for a Conversational or Voice agent, and only the console can turn long-term memory on. Files given to the run with `files` do it for any type, but read the [known limitation on `files`](/docs/agents/run-agent#known-limitations) first.

An agent that cannot run is not refused when you create it. Its run fails, and the execution says the agent `has no action or tool attached`. A plain `create-agent` call with only a `name` makes a Conversational agent with no knowledge base, so give it a skill or knowledge base tags before you run it.

## Agent types

`role` takes one of the five agent types the console offers. A type is a role label: every type is stored as the same kind of agent, and the console shows the role as a badge. `decider_agent` and `interop_agent` are node kinds declared in a `create-process-flow` manifest. It defaults to `"Conversational"`.

Older calls sent the role in `type`. That still works, but the result carries a `warning`, because `type` is the node kind (`skilled_agent`) everywhere else. If you send both, `role` is stored and `type` is ignored, with the same warning.

The label does not give an agent a different runtime. A Task agent and a Conversational agent run on the same engine. The label changes two things only: whether the agent can run without a skill (see running without a skill, above), and which console features apply to it, such as voice calls for a Voice agent and the OpenAI-compatible endpoint for a Proxy agent.

### Conversational

A chat agent. It answers messages, and keeps a conversation over several turns when you reuse one `session_id` on [run-agent](/docs/agents/run-agent). It is the default type. It can run without a skill when it has knowledge base tags: set `has_knowledge_base` to `true` and give at least one tag in `context`.

**Create a Conversational agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"create-agent","arguments":{"name":"Support assistant","role":"Conversational","has_knowledge_base":true,"context":["support_faq"]}}}'
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/create-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Support assistant","role":"Conversational","has_knowledge_base":true,"context":["support_faq"]}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.createAgent({
  name: 'Support assistant',
  role: 'Conversational',
  has_knowledge_base: true,
  context: ['support_faq']
})
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.create_agent(
    name="Support assistant",
    role="Conversational",
    has_knowledge_base=True,
    context=["support_faq"],
)
```

### Task

An agent that does a defined job from start to end, with no person in the conversation. A [schedule](/docs/automation/create-schedule) or a [trigger](/docs/automation/set-triggers) usually starts it. A Task agent always needs a skill. Find a real skill id with [list-skills](/docs/connectors/list-skills) and pass it in `skill_ids`.

**Create a Task agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"create-agent","arguments":{"name":"Nightly invoice check","role":"Task","skill_ids":["<skill id from list-skills>"]}}}'
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/create-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Nightly invoice check","role":"Task","skill_ids":["<skill id from list-skills>"]}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.createAgent({
  name: 'Nightly invoice check',
  role: 'Task',
  skill_ids: ['<skill id from list-skills>']
})
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.create_agent(
    name="Nightly invoice check",
    role="Task",
    skill_ids=["<skill id from list-skills>"],
)
```

### Voice

An agent that takes phone and browser calls. Speech is turned into text, the agent answers in the same way as a Conversational agent, and the answer is spoken back.

**Voice calls are console-only.** A call needs a Bland connection and voice settings, and only the console can set them. From the SDKs and the REST API you can create an agent with `role: "Voice"` and run it on text with `run-agent`. It follows the Conversational rule for running without a skill.

**Create a Voice agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"create-agent","arguments":{"name":"Front desk","role":"Voice","has_knowledge_base":true,"context":["clinic_hours"]}}}'
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/create-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Front desk","role":"Voice","has_knowledge_base":true,"context":["clinic_hours"]}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.createAgent({
  name: 'Front desk',
  role: 'Voice',
  has_knowledge_base: true,
  context: ['clinic_hours']
})
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.create_agent(
    name="Front desk",
    role="Voice",
    has_knowledge_base=True,
    context=["clinic_hours"],
)
```

### Proxy

Your agent behind an OpenAI-compatible endpoint, so that a framework that speaks the OpenAI API can call it. The endpoint is [`POST /v1/agents/{agentflowId}/chat/completions`](/docs/sdks-and-apis/chat-completions). A Proxy agent always needs a skill.

**The endpoint answers only after a version is promoted.** Until then it returns `409` with the code `not_ready`. A new agent has no versions, and the first version is promoted in the console (Agent Simulation). After that, [rollback-agent-version](/docs/agents/rollback-agent-version) can make any promoted version live. `run-agent` needs no promoted version, so you can test the agent before you promote it.

**Create a Proxy agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"create-agent","arguments":{"name":"Revenue answers","role":"Proxy","skill_ids":["<skill id from list-skills>"]}}}'
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/create-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"Revenue answers","role":"Proxy","skill_ids":["<skill id from list-skills>"]}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.createAgent({
  name: 'Revenue answers',
  role: 'Proxy',
  skill_ids: ['<skill id from list-skills>']
})
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.create_agent(
    name="Revenue answers",
    role="Proxy",
    skill_ids=["<skill id from list-skills>"],
)
```

### Interop

A connection to an agent that runs on another platform, over MCP or A2A. In the console, Interop has two directions. **Outbound** brings an external agent in: you register a connection to it, and then a [process flow](/docs/automation/create-process-flow) can hand a step to it through an external agent node. **Inbound** publishes one of your own agents so that external callers can reach it. **Inbound publishing is console-only.** No tool does it.

**From the SDKs, Interop means [register-peer-agent](/docs/agents/register-peer-agent)**, not `create-agent` with `role: "Interop"`. That `create-agent` call makes an ordinary Wexa agent with an Interop label: it runs on Wexa's own model and skills, and it always needs a skill.

Check the endpoint with [handshake-peer-agent](/docs/agents/handshake-peer-agent) first. An MCP external agent must expose exactly one tool, and the console's other outbound option, attaching an external agent's tools as skills, is console-only.

**Register an external agent**

**Wexa MCP server**

```bash
curl -sS -X POST https://fabric.wexa.ai/mcp/$FABRIC_PROJECT_ID \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "Mcp-Session-Id: $MCP_SESSION_ID" \
  -H "content-type: application/json" -H "accept: application/json" \
  -d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"register-peer-agent","arguments":{"name":"invoice-reconciler","endpoint":"https://agents.example.com/a2a/invoice-reconciler","protocol":"a2a","secret":"<the external agent'"'"'s token>"}}}'
```

**REST API**

```bash
curl -sS https://fabric.wexa.ai/v1/register-peer-agent \
  -H "authorization: Bearer $FABRIC_API_KEY" \
  -H "content-type: application/json" \
  -d '{"name":"invoice-reconciler","endpoint":"https://agents.example.com/a2a/invoice-reconciler","protocol":"a2a","secret":"<the external agent'"'"'s token>"}'
```

**TypeScript SDK**

```ts
import { Fabric } from '@wexa-fabric/sdk'

const fabric = new Fabric()
const result = await fabric.registerPeerAgent({
  name: 'invoice-reconciler',
  endpoint: 'https://agents.example.com/a2a/invoice-reconciler',
  protocol: 'a2a',
  secret: '<the external agent\'s token>'
})
```

**Python SDK**

```python
from wexa import Fabric

fabric = Fabric()
out = fabric.register_peer_agent(
    name="invoice-reconciler",
    endpoint="https://agents.example.com/a2a/invoice-reconciler",
    protocol="a2a",
    secret="<the external agent's token>",
)
```

The result carries a `connection_id`. Put it on an external agent node as `registration_id`; see [an external agent node, end to end](/docs/automation/create-process-flow#an-external-agent-node-end-to-end).

Every tool call works the same way: [How every tool call works](/docs/sdks-and-apis/how-every-tool-call-works).
