Cumulus TalosDocs
Open Talos
On this page

Agents

An agent is a flow graph plus its settings. You change it through its draft, check it, and publish immutable versions; runs execute a version, never the draft.

Create an agent

Start from a template, clone an existing agent, or import a Retell export. Each needs an Idempotency-Key.

TemplateChannelStarts as
blankcloud or phoneOne conversation node with an empty prompt.
cloud_taskcloudA task: takes the run input, works through it, ends. Task mode.
cloud_chatcloudA conversation over messages you send to the run. Chat mode.
phone_outboundphoneAn outbound call: greet, say who it is calling for, ask how to help. Records both channels.

POST /api/v1/agents takes name, template, channels and languages(en, es). A template must match a channel you choose. POST /api/v1/agents/{agent_id}/clone copies an agent's head, a version you name, or its draft if it was never published, into a new agent's first draft.

Import from Retell

POST /api/v1/agents/import turns a Retell conversation-flow agent export into a phone agent's draft. An import never goes live: test the draft, then publish it. A reference.id no agent has creates a new agent; the ID of an existing agent replaces its draft and leaves its live version running. Split the export: its conversationFlow is flow, the rest is agent; agent_version is Retell's version, kept as provenance. Retell LLM (single-prompt) agents are not accepted. Check an export first with POST /api/v1/agents/import/inspect: it compiles the same body, returns the diagnostics, and says what the import would do in target (a new agent, a new or replaced draft, and whether that draft has unpublished changes). It writes nothing.

POST /api/v1/agents/import

Example
{
  "reference": { "tenant_id": "<your environment's tenant_id>", "kind": "agent", "id": "fairway-rpc-outbound" },
  "agent_version": 6,
  "agent": { …the Retell agent object without conversationFlow, "version": 6, "webhook_url": null… },
  "flow": { …the export's conversationFlow: conversation_flow_id, version, nodes, tools… },
  "config": {
    "providers": {
      "llm": { "provider": "openai", "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } },
      "stt": { "provider": "elevenlabs", "model": "scribe_v2_realtime" },
      "tts": { "provider": "elevenlabs", "model": "eleven_flash_v2_5", "voiceId": "<a voice_id from GET /api/v1/voices>" }
    },
    "recording": { "required": true, "channels": "dual" }
  },
  "model_bindings": {
    "$.flow.model_choice": { "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } },
    "$.agent.post_call_analysis_model": { "model": { "model_id": "gpt-5.4-mini", "provider": "openai", "tier": "cumulus-served" } }
  }
}
  • reference.tenant_id is your environment's: GET /api/v1/environments lists production and sandbox with their tenant_id and which one your key is in (current).
  • config.providers chooses the models the agent runs on: a ready chat model from GET /api/v1/models for llm (with provider: "openai", the platform route for every catalog model), and a speech model and voice from GET /api/v1/voices. The Retell voice is replaced by that voice (warning voice_rebound).
  • model_bindings maps each model the export declares to a catalog model. Run inspect with an empty object first: every model_binding_required diagnostic names one path to bind (usually $.flow.model_choice and $.agent.post_call_analysis_model).
  • Set agent.webhook_url to null, or pin the same URL as config.webhook ({ url, secretRef: "<secret id>@<version>" }, a webhook_signing secret); a different URL is refused with webhook_mismatch.
  • config.telephony ({ trunkId, from }) is optional. Leave it out until your trunk and numbers exist, then add them with the agent.telephony edit (Phone).
  • Retell tools become HTTP actions. Inline secrets in tool headers or query values, and custom SIP headers, are refused (private_inline_value): move them to a secret.
  • Locales other than English and Spanish are dropped with a warning.
  • Replacing a draft with unpublished changes needs replace_draft with that draft's draft_id and revision (expected_revision); without it the import is refused with 409 conflict, reason draft_has_changes, so no client discards work by accident.
  • Importing the same body onto its unedited draft again returns that draft (200) and writes nothing.
OperationMCP toolKey rolePurpose
GET /api/v1/agentsagents_listviewerList agents with their head publication, open draft and last run; search, filter and order server-side
GET /api/v1/agents/{agent_id}agents_getviewerGet one agent
POST /api/v1/agentsagents_creatememberCreate an agent from a template (201; an idempotent replay returns 200)
DELETE /api/v1/agents/{agent_id}agents_deletememberDelete (archive) an agent: it leaves the catalog and takes no new drafts, publications or runs; history is kept
POST /api/v1/agents/{agent_id}/cloneagents_clonememberClone an agent's head, chosen version or never-published draft into a new agent's first draft (201; replay 200)
POST /api/v1/agents/importagents_importmemberImport a Retell agent export as a draft, never live: a new agent gets it as its first draft; an existing agent's draft is replaced and its head is unchanged. Replacing a draft with unpublished changes needs replace_draft {draft_id, expected_revision} naming it (409 reason draft_has_changes otherwise). Publish the draft with publications.publish (201; re-importing the same export onto its unedited draft returns 200)
POST /api/v1/agents/import/inspectagents_import_inspectmemberCompile a Retell export and say what agents.import would do (new agent, new or replaced draft, unpublished changes it would discard) without importing it

The flow graph

A flow has a global prompt, a start node and nodes joined by transitions. Each node has an instruction (a prompt, or static text to say) and edges whose conditions are prompts the model evaluates, equations on variables (==, contains, is_set, …) or always. Node kinds:

conversation``end``branch``transfer_call``function``extract_dynamic_variables``component

  • function nodes call a tool (an HTTP action you define), optionally waiting for its result.
  • extract_dynamic_variables nodes pull typed values out of the conversation into variables.
  • transfer_call nodes transfer a phone call, cold or warm, to a number or a {{variable}}.
  • component nodes run a reusable sub-flow; end ends the call or conversation.
  • Prompts use {{variables}}: a run's dynamic_variables fill them, and a phone run missing one the agent needs is refused (Dynamic variables).

Talos's canvas edits the same graph; its node positions are presentation only and never change a version's digest.

Drafts: edit and validate

An agent has at most one draft. POST …/draft opens one from the head (or from base_version). Every PATCH …/draft sends up to 100 typed edits with the draft_id and the expected_revision it was based on; if someone else edited first you get 409 conflict and reload. There are no free-form paths: each change is one of these edits.

Edit a draft

Example
PATCH /api/v1/agents/{agent_id}/draft
{
  "draft_id": "5b0e…",
  "expected_revision": 4,
  "edits": [
    { "type": "flow.prompt", "text": "You are Maya, calling on behalf of Acme Dental about {{appointment_time}}." },
    { "type": "languages.set", "languages": ["en", "es"] },
    { "type": "agent.telephony", "value": { "trunkId": "my-twilio-trunk", "from": "+14155550100" } }
  ]
}

agent.name``agent.settings``agent.analysis``agent.provider``agent.telephony``agent.recording``flow.prompt``node.name``node.instruction``node.transitions``node.configure``node.create``node.remove``flow.configure``component.create``component.start``component.remove``transfer.credential``channels.set``languages.set``trigger.put``trigger.remove``action.put``action.remove``notification.put``notification.remove``knowledge.set``cloud.io.set``layout.set

POST …/draft/validate compiles the draft and returns { ok, version, digest, diagnostics }: the version and digest it would publish, and every warning or error. An error blocks publishing. A finding that has a mechanical fix lists it in fixes as { id, label, edits }: send edits as a PATCH …/draft with the revision you validated, then validate again. A draft that changed since refuses the fix with 409 conflict.

OperationMCP toolKey rolePurpose
GET /api/v1/agents/{agent_id}/draftdrafts_getviewerRead the open draft
POST /api/v1/agents/{agent_id}/draftdrafts_openmemberOpen a draft from the head or a chosen version
PATCH /api/v1/agents/{agent_id}/draftdrafts_editmemberApply domain edits to the draft (revision compare-and-set)
DELETE /api/v1/agents/{agent_id}/draftdrafts_discardmemberDiscard the draft (revision compare-and-set)
POST /api/v1/agents/{agent_id}/draft/rebasedrafts_rebasememberRebase the draft onto the live version after a head change (keeps its definition; 409 head_moved when live moved again)
POST /api/v1/agents/{agent_id}/draft/validatedrafts_validatememberCompile the draft and return diagnostics and the would-be digest

Publish and roll back

POST …/publications with the draft_id, expected_revision and the next version (0 for the first) compiles the draft into an immutable publication. By default it becomes the head, and runs that name no version use it from then on. Options:

  • "live": false publishes a test version: you can run it by version, but the head does not move and its triggers are not registered. Test-publishing a draft revision that already has a test version returns that version instead of a new one, so run the version in the response.
  • "keep_draft": true keeps the draft open for more changes.

To go live safely: validate, publish with "live": false, run your test suites against that version, then promote it.

POST …/head moves the head to any existing version: promote a test version, or roll back. It takes expected_head_version, the head you saw (null for none), and refuses the move with 409 conflict if the head has moved since. Runs already admitted keep the version they started with.

OperationMCP toolKey rolePurpose
POST /api/v1/agents/{agent_id}/publicationspublications_publishmemberPublish the draft as the next immutable version; moves head unless live is false (201; replay 200)
GET /api/v1/agents/{agent_id}/publicationspublications_listviewerList publications of an agent
GET /api/v1/agents/{agent_id}/publications/{version}publications_getviewerGet one publication with its compiled plan
POST /api/v1/agents/{agent_id}/headpublications_promotememberMove head to an existing version (manual promote or rollback), rebasing the named open draft in the same transaction

Actions and governed effects

Everything an agent does outside the conversation is an action: calling your HTTP endpoint, transferring or ending a call, leaving a voicemail, scheduling a callback, searching knowledge, delivering a webhook. Each time a run takes one, Talos checks it against the run's live grant just before sending, records the decision, and settles it as an effect with a receipt (intended → sending → succeeded | failed | uncertain). A denied action sends nothing. An unacknowledged one is uncertain and is never blindly resent. Read them with GET /api/v1/runs/{run_id}/effects.

Built-in actionWhat it does
webhook.deliverDeliver run webhook
http.requestCustomer HTTP action
call.transferTransfer call
run.endEnd call or conversation
call.voicemailHandle voicemail
callback.scheduleSchedule callback
knowledge.searchSearch knowledge
call.dialDial outbound call
livekit.roomCreate media room
livekit.dispatchDispatch voice agent
run.analyzeAnalyze run

Your own HTTP actions (action.put) call HTTPS endpoints with none, bearer, basic or header authentication, the credential held as a secret. Its response_variables set flow variables from the action's 2xx JSON response, each read at a path:{"balance": "$.account.balance"} makes the nested field {{balance}}.

OperationMCP toolKey rolePurpose
GET /api/v1/actionsactions_listviewerList the action descriptors available to publications
GET /api/v1/voicesvoices_listviewerList the speech models and platform TTS voices an agent can use

Test before going live

Use Test an agent for simulated caller suites, verdicts and browser voice sessions. Follow the publish-and-test recipe before promotion.

Try it in the browser

Note

POST /api/v1/test-sessions starts a browser voice test of one version of a phone agent: it admits a web run marked as a test and returns a short-lived LiveKit room token, so you talk to the agent from Talos. The same Idempotency-Key rejoins the live test with a fresh token.

OperationMCP toolKey rolePurpose
POST /api/v1/test-sessionstest_sessions_creatememberStart a browser voice test of one agent version: admits a web run and returns a short-lived LiveKit room token (the same Idempotency-Key rejoins the live run with a fresh token)
Content version b043053dMarkdown source
Agents · Cumulus Talos docs