Docs

Agents

Consumer-facing guide to starting, monitoring, and resuming agent sessions on the Amdahl platform. This page covers the agents.* tool family end to end. For a tool-by-tool reference see API reference: workflows. For authentication see Authentication. For how sessions differ from substrate and artifacts see Sessions.

What are agents?

An agent session is one execution of a named agent profile on a task. Each session gets its own conversation log, tool surface, turn budget, token ledger, and pause state. You start a session, monitor its progress, answer pauses when the agent needs input, and pick up the final artifact when it completes.

Profiles are named configurations. content_writer writes content end to end. researcher synthesizes research reports. copilot is a general-purpose assistant with a broad tool surface. Each profile decides which tools the agent can call, how many turns it gets, and what deliverable it produces.

Use an agent when the work requires more than one tool call, planning, judgment, or a pause for human input. Call a tool directly when the work is atomic.

When to use which

Your needUse
A single atomic read or write (list artifacts, run a SQL query, fetch a KB doc)Tools directly: MCP data.query, REST GET /data/query, etc.
Multi-step work that should plan, call several tools, and produce a deliverableAn agent session via agents.start
End-to-end content creation with research, outlining, writing, evaluationcontent_writer profile
Rigorous research over substrate, KB, and clusters with citationsresearcher profile
General task that might or might not need toolscopilot profile
Custom tool subset or custom system promptCustom profiles are not yet self-serve. File a request to extend the built-in catalog.

Starting an agent

Use agents.start (MCP) or POST /agents/run (REST). You must supply a profile_id and a task. input_params is a free-form object the profile interprets (for content_writer, common keys are channel, content_type, audience, voice_profile_id, reference_document_ids).

MCP

json
{
  "name": "agents.start",
  "input": {
    "profile_id": "content_writer",
    "task": "Write a LinkedIn post about our Q2 earnings call highlights",
    "input_params": {
      "channel": "linkedin",
      "content_type": "linkedin_post"
    }
  }
}

REST

bash
curl -X POST https://api.amdahl.com/api/platform/v1/agents/run \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "profile_id": "content_writer",
    "task": "Write a LinkedIn post about our Q2 earnings call highlights",
    "input_params": { "channel": "linkedin", "content_type": "linkedin_post" }
  }'

Response shape

By default the call is async and returns immediately:

json
{
  "success": true,
  "session_id": "2f0a8c11-5d1b-4e3e-9b0a-6b9a2d4f8a11",
  "status": "queued",
  "profile_id": "content_writer",
  "created_at": "2026-04-14T18:22:14.509Z",
  "async": true
}

Set async: false to block for up to 60 seconds and receive the fully-populated session row when the agent finishes. If the agent pauses or the call times out, the response carries success: false with a structured error.code of enqueue_failed, plus the latest session snapshot so the caller can poll or resume.

Monitoring progress

You have two supported options. Webhook delivery is on the roadmap; see the Webhooks section below.

1. Poll agents.status

MCP:

json
{ "name": "agents.status", "input": { "session_id": "<uuid>", "include_events": false } }

REST: GET /api/platform/v1/agents/:session_id/status

Response shape:

json
{
  "success": true,
  "session_id": "<uuid>",
  "status": "running",
  "profile_id": "content_writer",
  "task": "Write a LinkedIn post...",
  "created_at": "2026-04-14T18:22:14.509Z",
  "updated_at": "2026-04-14T18:23:02.120Z",
  "started_at": "2026-04-14T18:22:15.102Z",
  "completed_at": null,
  "turns_used": 4,
  "result_artifact_id": null,
  "result_summary": null,
  "progress": {
    "turnsCompleted": 4,
    "toolsUsed": [
      { "toolId": "knowledge_base.search", "count": 3 },
      { "toolId": "data.query", "count": 1 }
    ],
    "currentState": "tool_running",
    "tokensUsed": { "input": 12480, "output": 3210, "cacheRead": 0, "cacheCreate": 0 }
  }
}

Set include_events: true to add the last 50 persisted AgentEvent entries. Poll at a pace that matches your UX: a dashboard can poll every 2 to 5 seconds; a batch integration can poll every 15 to 30 seconds or rely on streaming. See Rate limits for the recommended cap of 1 request per second.

2. Stream via Server-Sent Events

REST: GET /api/platform/v1/agents/:session_id/stream

The stream opens with a state event carrying the current session summary, then emits a progress event for each new AgentEvent as it is persisted. A :ping comment fires every 15 seconds to keep proxies from idling the connection. The stream closes automatically when the session reaches a terminal status (complete, error, canceled, awaiting_input) with a final state frame. If the connection exceeds the 10 minute cap, the server emits event: timeout and closes; clients reconnect to keep streaming.

Event shape on the wire:

code
event: progress
data: {"type":"tool_start","toolId":"knowledge_base.search","inputSummary":"{\"query\":\"Q2 earnings\"}","timestamp":"2026-04-14T18:23:01.222Z"}

event: progress
data: {"type":"tool_complete","toolId":"knowledge_base.search","durationMs":1240,"resultSummary":"...","timestamp":"2026-04-14T18:23:02.462Z"}

event: state
data: {"session_id":"...","status":"complete","result_artifact_id":"..."}

AgentEvent types you will see: tool_start, tool_complete, tool_error, thinking, turn_complete, paused, completed, error.

3. Webhooks (roadmap)

Webhook subscriptions on agent.session.completed, agent.session.paused, and agent.session.failed are planned. Use the existing polling or SSE paths until webhooks land. See Webhooks for what already works.

Handling pauses

When an agent needs user input the session transitions to status: 'awaiting_input' and the response carries a pending_input object with three fields: pending_input_type, pending_input_schema, and pending_input_context. Your reply must match the schema exactly; the resume endpoint validates against it before the worker picks up the resume job.

There are three pause types.

Approval

The agent wants a sign-off on a draft, an outline, or a destructive step.

json
{
  "pending_input_type": "approval",
  "pending_input_schema": {
    "type": "object",
    "properties": {
      "approved": { "type": "boolean" },
      "feedback": { "type": "string", "maxLength": 5000 }
    },
    "required": ["approved"],
    "additionalProperties": false
  },
  "pending_input_context": { "tool_id": "pages.create", "tool_use_id": "toolu_01..." }
}

Resume with approval or rejection:

json
{ "approved": true }
json
{ "approved": false, "feedback": "Make the hook sharper; lead with the revenue number" }

Question

The synthetic ask_user tool pauses the session to ask a single free-form question. The question is stored in both the schema description and the pending_input_context.question field.

json
{
  "pending_input_type": "question",
  "pending_input_schema": {
    "description": "What tone do you want?",
    "type": "object",
    "properties": { "response": { "type": "string", "maxLength": 10000 } },
    "required": ["response"],
    "additionalProperties": false
  },
  "pending_input_context": { "question": "What tone do you want?" }
}

Resume:

json
{ "response": "Professional but approachable; direct, no filler" }

Continue or finish

Fires when the agent exhausts its maxTurns budget without finishing. You decide whether to grant more turns or wrap up with whatever work is done.

json
{
  "pending_input_type": "continue_or_finish",
  "pending_input_schema": {
    "type": "object",
    "properties": {
      "action": { "enum": ["continue", "finish"] },
      "additional_turns": { "type": "integer", "minimum": 1, "maximum": 50 }
    },
    "required": ["action"],
    "additionalProperties": false
  },
  "pending_input_context": { "reason": "max_turns", "turns": 25 }
}

Resume with a grant or a wrap-up:

json
{ "action": "continue", "additional_turns": 10 }
json
{ "action": "finish" }

REST resume

bash
curl -X POST https://api.amdahl.com/api/platform/v1/agents/$SESSION_ID/resume \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "input": { "approved": true } }'

Response:

json
{
  "session_id": "<uuid>",
  "status": "queued",
  "message": "Resume job enqueued",
  "job_id": "agent-session-<uuid>-resume"
}

On validation failure the handler returns status: 'invalid_input' and a validation_error string describing the mismatch. On state mismatch (session not currently awaiting input) the handler returns status: 'invalid_state' with the current session status.

Cancellation

Sessions can be canceled at any non-terminal state. agents.cancel flips status to canceled and stamps completed_at. The worker observes the new status at the next safe checkpoint and stops making tool calls. Best-effort removal of any queued job runs in parallel; failures here are logged but do not block the cancel.

MCP:

json
{
  "name": "agents.cancel",
  "input": { "session_id": "<uuid>", "reason": "Scope changed; rerun with new brief" }
}

REST: POST /api/platform/v1/agents/:session_id/cancel

Already-terminal sessions (complete, error, canceled) return status: 'cannot_cancel' with the current status echoed back. The reason field is optional and recorded in operator logs.

Built-in profiles

Profile idDescriptionTool subsetMax turnsProduces
content_writerEnd-to-end content creation: research, outline, write, evaluate, finalize.content.*, data.*, context.*, context_substrate.*, context_entries.*, artifacts.create, artifacts.update, knowledge_base.search25platform_artifact of type content_piece (or subtype like linkedin_post, blog_post when input_params imply one)
researcherRigorous research over substrate + knowledge base with citations. Read-heavy.data.*, context.*, context_substrate.query, knowledge_base.search, knowledge_base.chat, artifacts.create20platform_artifact of type research_report with structured summary, sub_questions, key_findings, gaps, citations
copilotGeneral-purpose assistant. Broad tool surface minus destructive and privileged admin operations.All registered tools except artifacts.delete, artifacts.archive, everything under api_keys.*, oauth_clients.*, audit_log.*15Optional: the agent decides whether a platform_artifact is the right deliverable

Fetch the live list via agents.profiles_list (MCP) or GET /api/platform/v1/agents/profiles (REST). The response includes each profile's id, name, description, version, max_turns, model, and produces_artifact_type. Internal fields (system prompt, initial message template, tool id list) are deliberately omitted.

Results

When status: 'complete', the status response carries:

  • result_artifact_id: UUID of the final platform_artifact when the profile produces one. null for profiles (like copilot) that may return a chat-only response.
  • result_summary: short narrative the agent wrote describing what was produced.

Fetch the artifact itself.

MCP:

json
{ "name": "artifacts.get", "input": { "id": "<result_artifact_id>", "include_versions": true } }

REST: GET /api/platform/v1/artifacts/:id

Artifacts carry the rendered output in content_markdown and/or content_json, plus structured metadata like sources, citations, and tags. Every profile that produces an artifact writes citations into content_json so downstream viewers can render a source list.

Error handling

Terminal error states land in status: 'error' with a message on the top-level session_error field (distinct from the envelope error field used for invalid inputs).

Common error conditions and how to recover:

  • Anthropic rate limits. The runner retries with jittered exponential backoff (up to 12 attempts, max 8s delay). A persistent failure lands in status: 'error'. Retry the task after the rate window passes by calling agents.start again.
  • Tool execution failures. A single failing tool call does not end the session; the runner injects an error tool_result and lets the agent decide what to do. Chronic tool failure shows up in the event log as repeated tool_error events. Inspect via agents.status with include_events: true.
  • Invalid resume payload. agents.resume returns status: 'invalid_input' with a validation_error. Fix the payload to match pending_input_schema and retry.
  • Max turns hit. The session pauses with continue_or_finish. See the pause section.
  • Session not found / forbidden. Returns status: 'not_found' on both missing rows and cross-business access, so one tenant cannot probe another's session ids.

See API reference: errors for the full error code catalogue and recovery matrix.

Security

Every agent runs under the caller's identity and scopes. The worker builds a tool-auth context that mirrors the HTTP translator: the agent sees exactly the tool surface the caller can execute. Scope checks happen at tool-dispatch time, so even if a profile references a tool beyond the caller's scope, that tool is silently dropped from the runtime tool list.

  • Agents cannot escalate privileges. If your API key or user cannot call a tool directly, the agent cannot call it for you.
  • Every tool call the agent makes is written to the platform audit log with the caller's identity and correlation id. The audit trail is the same one you get for direct REST or MCP calls.
  • Response redaction runs on every tool result. Secrets are masked with [MASKED] and paired with a <field>_masked sentinel so clients know a value existed without ever seeing it.
  • The agent's system prompt receives the caller's businessId for grounding but never renders raw credentials or tokens.
  • The SSE stream authenticates before flushing headers so auth failures return clean HTTP status codes, not broken streams.

Quick recipes

  • Draft a blog post. agents.start with profile_id: 'content_writer', task: 'Draft a 1200-word blog post about X', input_params: { channel: 'blog', content_type: 'blog_post' }. Poll until awaiting_input on outline approval, resume with { approved: true }, then fetch the artifact when complete. See recipes/draft-blog-post.
  • Run comprehensive research on topic X. agents.start with profile_id: 'researcher', task: 'Research how mid-market SaaS companies are adopting usage-based pricing', optional input_params: { must_cover: ['billing models', 'churn impact', 'expansion revenue'] }. The result is a single research_report artifact with citations. See recipes/run-research.
  • Ask a general question that might need tools. agents.start with profile_id: 'copilot', task: '<question or instruction>'. Poll status; the agent decides whether to answer in chat (check result_summary) or write an artifact (check result_artifact_id).

Rate limits

Per-tenant rate limits on agents.start, agents.resume, and the SSE stream endpoint are on the roadmap. Until they ship, the platform relies on the global per-IP limiter (see Rate limits) and worker queue concurrency to protect capacity. Expect bursts of 10+ concurrent sessions to enqueue cleanly and process at the worker pool's rate.

See also