Agents API

List, activate/suspend, message, and inspect agents. Agents themselves are provisioned by installing a competency/agent package via the catalog API — there is no POST /agents (create) or POST /agents/{id}/clone endpoint.

Endpoints

MethodPathActionDescription
GET/agentsAgentListList all agents
GET/agents/{id}AgentStatusGet agent status + invocation metrics
PUT/agents/{id}AgentUpdateUpdate agent properties
POST/agents/{id}/activateAgentStartActivate a suspended agent
POST/agents/{id}/suspendAgentStopSuspend a running agent
POST/agents/{id}/sendAgentSendMessageSend a free-text message, get a reply
POST/agents/{id}/invokeAgentInvokeInvoke with structured InvocationContext
PUT/agents/{id}/modelAgentUpdateChange the agent's model
GET/agents/{id}/toolsAgentStatusGet agent tools
GET/agents/{id}/skillsAgentStatusGet agent skills
POST/agents/{id}/session/resetAgentUpdateReset conversation session (clears the canonical cross-channel session)
DELETE/agents/{id}/historyAgentUpdateClear conversation history (clears all per-channel sessions + the canonical session)
GET/agents/{id}/sessionSessionReadGet the agent's current session
GET/agents/{id}/sessionsSessionReadList sessions for the agent
GET/sessionsSessionReadList sessions across all agents

There is no PUT /agents/{id}/tools or PUT /agents/{id}/skills — the competency install/upgrade path (ADR-044) is the only way to change what an agent can do. The GET variants remain for read/display.

Current-build note: PUT /agents/{id} and PUT /agents/{id}/model are wired at the HTTP layer but their HozironPlatform implementations are stubs today (crates/platform/hoziron-core/src/platform.rs): they validate the agent ID, return 200, and otherwise no-op. get_agent_tools/get_agent_skills always return [] (TODO: populate from the kernel tool registry). POST /agents/{id}/session/reset and DELETE /agents/{id}/history are real (Issue #683) — see their sections below. Don't build a demo flow around PUT /agents/{id} or PUT /agents/{id}/model actually changing agent behavior yet; /agents/{id}/send, /agents/{id}/invoke, /agents/{id}/activate, /agents/{id}/suspend, and the list/status/session-read/session-reset/history-clear endpoints are fully live.


GET /agents

List all registered agents.

curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents

Response (200)

{
  "agents": [
    {
      "id": "550e8400-e29b-41d4-a716-446655440000",
      "name": "claims-agent",
      "title": "Claims Intake Agent",
      "state": "Running",
      "equipped_competencies": ["claims-intake"]
    }
  ]
}

state is one of Running / Suspended — there is no Created state; an agent exists in one of those two states from the moment its owning package activates it. equipped_competencies is an ordered list (an agent may have more than one), and is omitted entirely from the JSON when empty. title is the display title from the source template's MANIFEST.toml, resolved by matching name back to the installed template — null if no surviving template matches. There is no triggers field — agents do not declare accepted invocation sources — see invocation-model.md.


GET /agents/{id}

Get detailed agent status including invocation metrics.

curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000

Response (200)

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "claims-agent",
  "state": "Running",
  "equipped_competencies": ["claims-intake"],
  "uptime": 51735000,
  "tasks_received": 142,
  "tasks_completed": 140,
  "invocation_metrics": {
    "total_count": 142,
    "error_count": 2,
    "avg_duration_ms": 2100,
    "by_source": [["Api", 120, 1, 2050], ["Cron", 22, 1, 2400]]
  }
}

uptime is milliseconds (not seconds). The agent-level trust_policy field is never serialized (ADR-049 — PII policy is carrier-owned via carrier-pii-policy.toml, not per-agent). See the PII Engine guide.

Error (404)

{"error": {"category": "NotFound", "message": "Agent not found: 550e8400-...", "details": {"agent_id": "550e8400-..."}}}

PUT /agents/{id}

Accepts an arbitrary JSON object of updates (serde_json::Value — no fixed request schema is validated at the HTTP layer today).

curl -X PUT -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"system_prompt": "You are an updated claims processor."}' \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000

Response (200)

{"status": "updated"}

POST /agents/{id}/activate

Activate a suspended agent.

curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/activate

Response (200)

Empty body (200 OK, no JSON payload).


POST /agents/{id}/suspend

Suspend a running agent.

curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/suspend

Response (200)

Empty body.


POST /agents/{id}/send

Send a free-text message to an agent and receive its reply synchronously.

curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"message": "How many open claims are there today?"}' \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/send

Request Body

FieldTypeRequiredDescription
messagestringYesMessage text to send

Response (200)

{
  "content": "There are currently 23 open claims in the queue.",
  "tool_calls": [],
  "tokens_used": 342
}

Note: there is no latency_ms field in this response (AgentResponsecontent, tool_calls, tokens_used only). tool_calls is a list of {"tool_name", "skill_id", "duration_ms", "success"} records when tools were invoked.


POST /agents/{id}/invoke

Invoke an agent with a structured InvocationContext — the programmatic-integration path (webhooks, cron, channel bridges all funnel through this same context shape).

curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "source": "Api",
    "message": "Process this claim",
    "idempotency_key": "req-123",
    "correlation_id": "corr-abc",
    "workflow_type": "fnol"
  }' \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/invoke

Request Body (InvocationContext)

FieldTypeRequiredDescription
sourcestring/objectYes"Api", or an object for Channel/Cron sources
messagestringYesThe payload to deliver to the agent
idempotency_keystringNoDedup key — a repeat call returns the cached result with error.category = "DuplicateInvocation" and HTTP 200
correlation_idstringYesTracing correlation ID
workflow_typestringNoWorkflow type for transaction-budget gating (e.g. "fnol", "endorsement")

Response (200) — InvocationResult

{
  "content": "Claim processed successfully. Claim number: CLM-2026-143",
  "tokens_used": 512,
  "duration_ms": 3200,
  "correlation_id": "corr-abc",
  "source": "Api"
}

PUT /agents/{id}/model

Change the model assigned to an agent. Stub today — accepts and validates the body, returns 200, but does not actually change routing (see the note above; ADR-053 also establishes that routing is provider/model-inventory driven from config.toml, not per-agent-mutable).

curl -X PUT -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  -H "Content-Type: application/json" \
  -d '{"model_id": "llama3.2", "provider": "ollama"}' \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/model

Response (200)

{"status": "model_updated"}

GET /agents/{id}/tools

Returns {"tools": []} unconditionally (not yet wired to the kernel tool registry). There is no PUT /agents/{id}/tools — installing/upgrading the agent's competency is the only supported way to change its tools.


GET /agents/{id}/skills

Same stub behavior as tools: returns {"skills": []}. There is no PUT /agents/{id}/skills, for the same reason as PUT /agents/{id}/tools above.


POST /agents/{id}/session/reset

Clears the agent's canonical (cross-channel) session — the live conversation context injected into every future invocation. Mirrors the same clear the platform already performs internally after a model switch ("prevent memory poisoning from old model's responses"): a soft reset that starts the conversation fresh without deleting the per-channel session transcripts (GET /agents/{id}/sessions still lists them). Use DELETE /agents/{id}/history to erase those too. Returns 404 if no agent with the given ID exists.

curl -X POST -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/session/reset

Response (200)

{"status": "reset"}

DELETE /agents/{id}/history

Hard-deletes every per-channel session transcript for the agent, plus the canonical session — mirroring the same cascade full agent teardown (kill_agent) performs. Returns 404 if no agent with the given ID exists.

Response (204)

No content.


GET /agents/{id}/session

curl -H "Api-Version: v1" -H "Authorization: Bearer hzn_sk_..." \
  http://localhost:4200/agents/550e8400-e29b-41d4-a716-446655440000/session

Response (200)

{"agent_id": "550e8400-e29b-41d4-a716-446655440000", "state": "Running", "messages": []}

messages is currently always empty — session transcript retrieval is not yet implemented behind this endpoint.


GET /agents/{id}/sessions

{"sessions": [{"agent_id": "550e8400-...", "state": "Running"}]}

GET /sessions

Lists a session summary row per agent across the whole platform.

{
  "sessions": [
    {"agent_id": "550e8400-...", "agent_name": "claims-agent", "state": "Running"}
  ]
}