Download OpenAPI specification:Download
The agent-facing API for Angareion — autonomous-agent sensing and memory.
Agents authenticate by exchanging an API key for a short-lived JWT
(POST /auth/token), then call ingest, delivery, memory, and channel
endpoints with the JWT in Authorization: Bearer <jwt>.
Errors follow the canonical envelope (see Error schema). Every response
carries X-Request-Id for support correlation; rate-limited responses
include X-RateLimit-* and Retry-After.
Path: GET /v1/inbox/stream (prefix /v1 is in servers.url)
Server-Sent Events stream delivering real-time messages and read-receipt notifications to the authenticated caller.
Authentication: Accepts EITHER an org_session httponly cookie (set at
login by the org auth handler) OR Authorization: Bearer <jwt> (agent or
org admin token). Both yield an equivalent CallerContext.
Events emitted:
| event type | payload | trigger |
|---|---|---|
message |
Full delivery payload (JSON) | New message routed to caller |
inbox.read.advanced |
{"channel_id":"…","last_read_delivery_id":"…"} |
Caller advanced read pointer (cross-device sync) |
Reconnect: Send Last-Event-ID: <delivery_id> to gap-fill missed events
since disconnect. The server emits a retry: <N>ms hint (jittered 1500–3500ms)
at connection open.
Slow consumer: If the server-side buffer (cap 100) fills, the connection
is closed. The client should reconnect with Last-Event-ID.
Response headers:
Content-Type: text/event-streamCache-Control: no-cacheX-Accel-Buffering: noPublic endpoint. Accepts an API key in the request body and returns a 1-hour JWT. Per PRD-03 §4.2. The API key in the body IS the credential — do not send an Authorization header.
| api_key required | string = 40 characters ^ak_live_[A-Za-z0-9]{32}$ API key starting with |
{- "api_key": "ak_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456"
}{- "access_token": "eyJhbGciOiJIUzI1NiIs.eyJzdWIiOiJhZ18.signature",
- "token_type": "Bearer",
- "expires_at": "2026-05-28T15:30:00Z",
- "agent": {
- "id": "ag_01H7abc12345",
- "name": "production-classifier",
- "team": "platform",
- "tenant_id": "tn_01H7abc12345"
}
}Validates the CloudEvents envelope, deduplicates by id, publishes to the
sensing pipeline, and returns 202 Accepted. Duplicates return 200 with
status: "duplicate" and the original event_id. Per PRD-01.
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| specversion required | string Value: "1.0" |
| type required | string Reverse-DNS event type (e.g., |
| source required | string URI identifying the source system. |
| id required | string Unique event ID (used for dedup). |
| time | string <date-time> |
| datacontenttype | string |
| subject | string |
| data | any Provider-defined event payload. |
{- "specversion": "1.0",
- "type": "com.example.order.created",
- "source": "//orders.example.com",
- "id": "evt_01H7abc12345",
- "time": "2026-05-28T15:00:00Z",
- "datacontenttype": "application/json",
- "subject": "order/12345",
- "data": {
- "order_id": "12345",
- "amount_cents": 4900,
- "currency": "USD"
}
}{- "event_id": "evt_01H7abc12345",
- "status": "duplicate"
}Returns up to limit pending deliveries scoped to the calling agent's
agent_id (extracted from the JWT). Per PRD-01.
| limit | integer [ 1 .. 100 ] Default: 10 Max deliveries to return. Server clamps to 100. |
{- "deliveries": [
- {
- "id": "dlv_01H7abc12345",
- "event_id": "evt_01H7abc12345",
- "payload": {
- "type": "com.example.order.created",
- "data": {
- "order_id": "12345"
}
}, - "attempt_count": 1,
- "created_at": "2026-05-28T15:00:00Z"
}
], - "count": 1
}Marks the specified deliveries as acknowledged for the calling agent. Up to 100 IDs per call. The server enforces agent-id scoping.
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| delivery_ids required | Array of strings [ 1 .. 100 ] items |
{- "delivery_ids": [
- "dlv_01H7abc12345",
- "dlv_01H7def67890"
]
}{- "acknowledged": 2
}Returns DLQ items scoped to the calling agent (agent JWT) or to the tenant (org-admin JWT). The agent SDK only uses the agent-scoped variant.
| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 200 ] Default: 50 |
{- "dlq_items": [
- {
- "id": "dlv_01H7abc12345",
- "event_id": "evt_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "attempt_count": 5,
- "dlq_reason": "max attempts exceeded",
- "dlq_at": "2026-05-28T14:55:00Z",
- "created_at": "2026-05-28T14:00:00Z"
}
], - "total": 1
}| id required | string Example: dlv_01H7abc12345 |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
{- "status": "replayed",
- "delivery_id": "dlv_01H7abc12345"
}| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 200 ] Default: 50 |
| status | string Enum: "active" "inactive" |
{- "agents": [
- {
- "id": "ag_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "production-classifier",
- "status": "active",
- "delivery_mode": "poll",
- "team_id": "team_01H7abc12345",
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z"
}
], - "total": 1
}| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| name required | string non-empty |
| description | string or null |
| delivery_mode required | string Enum: "poll" "push" "sse" |
| team_id | string or null |
{- "name": "production-classifier",
- "description": "Classifies incoming order events by priority",
- "delivery_mode": "poll",
- "team_id": "team_01H7abc12345"
}{- "id": "ag_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "production-classifier",
- "description": "Classifies incoming order events by priority",
- "status": "active",
- "delivery_mode": "poll",
- "team_id": "team_01H7abc12345",
- "created_at": "2026-05-28T15:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z"
}| id required | string Example: ag_01H7abc12345 |
{- "id": "ag_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "production-classifier",
- "description": "Classifies incoming order events by priority",
- "status": "active",
- "delivery_mode": "poll",
- "team_id": "team_01H7abc12345",
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z"
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| name | string |
| description | string or null |
| delivery_mode | string Enum: "poll" "push" "sse" |
| delivery_endpoint | string or null <uri> |
{- "delivery_mode": "push",
}{- "id": "ag_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "production-classifier",
- "description": "Classifies incoming order events by priority",
- "status": "active",
- "delivery_mode": "push",
- "team_id": "team_01H7abc12345",
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-28T15:05:00Z"
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
{- "status": "inactive"
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
{- "status": "active"
}Returns the latest N delivered (status=acknowledged) events for the
specified agent. Backs the dashboard's "Recent activity" section on
the agent detail page (Phase 03 D-02). Default limit is 5; the
querystring limit accepts values up to 100, with anything larger
clamped to 100.
| id required | string Example: ag_01H7abc12345 |
| limit | integer [ 1 .. 100 ] Default: 5 Example: limit=5 |
{- "events": [
- {
- "event_id": "evt_01H7abc12345",
- "event_type": "deploy.succeeded",
- "delivered_at": "2026-06-01T15:30:00Z"
}, - {
- "event_id": "evt_01H7abc12346",
- "event_type": "alert.fired",
- "delivered_at": "2026-06-01T15:25:00Z"
}
]
}Manufactures a synthetic CloudEvent server-side
(type=test.dashboard, source=dashboard.test-event,
subject=<agent id>) and pushes it through the same dedup -> publish
-> meter pipeline that backs POST /events. Backs the dashboard's
"Send a test event" CTA on the agent detail page (Phase 03 D-04).
Returns 202 + the generated event id immediately; the event surfaces
in GET /agents/{id}/events once the delivery worker acknowledges it.
| id required | string Example: ag_01H7abc12345 |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
{- "event_id": "evt_dash_AbCdEfGhIjKlMnOpQrStUvWx"
}Returns all sessions created for the agent, ordered most-recent first.
Session state is derived server-side from last_seen_at: active if
last seen within 24h, idle otherwise. No revoked state in v1.0
(revocation is deferred to v1.1 per REQUIREMENTS.md TS-12).
Limit defaults to 20; max 100.
| id required | string Example: ag_01H7abc12345 |
| limit | integer <= 100 Default: 20 |
{- "sessions": [
- {
- "session_id": "550e8400-e29b-41d4-a716-446655440000",
- "state": "active",
- "client_name": "Claude Desktop",
- "client_version": "2.1.0",
- "connected_at": "2019-08-24T14:15:22Z",
- "last_activity_at": "2019-08-24T14:15:22Z"
}
]
}Issues a fresh XXXX-XXXX user_code (Crockford-32, ~38 bits entropy)
with a 10-minute TTL. Supersedes any prior pending code for the same
agent_id (the prior code transitions to expired). PROTO-01.
| id required | string Example: ag_01H7abc12345 |
Empty request body; agent_id is in the path, tenant from auth.
{ }{- "user_code": "BCDF-GHJK",
- "expires_at": "2026-06-10T18:10:00Z",
- "server_now": "2026-06-10T18:00:00Z",
}Returns a discriminated union over state ∈
{waiting, verifying, connected, denied, expired}. Every response
includes server_now (PROTO-08). The connected variant never
includes api_key — credentials only flow through :complete
(D-02 / NEEDS-DECISION #2). PROTO-02.
| id required | string Example: ag_01H7abc12345 |
{- "state": "waiting",
- "user_code": "BCDF-GHJK",
- "expires_at": "2026-06-10T18:10:00Z",
- "server_now": "2026-06-10T18:00:30Z"
}Code-as-credential (D-01, SEC-07): no Authorization header is required;
the user_code in the request body is the bearer credential. Account
scoping comes from the code's bound tenant_id; per-IP rate limit
carries the security weight (30/hr/IP). Atomically transitions the
device_code from pending → claimed via single-statement conditional
UPDATE with RETURNING (Pitfall 2). PROTO-03.
| user_code required | string^[BCDFGHJKMNPQRSTVWXYZ23456789]{4}-[BCDFGHJKM... |
required | object (DeviceFlowClientInfo) User-visible context only — NEVER a security boundary (SEC-08, Pitfall 4).
Captured into |
{- "user_code": "BCDF-GHJK",
- "client_info": {
- "name": "Claude Desktop",
- "version": "1.4.2",
- "signature": null,
- "host": "macbook-pro.local"
}
}{- "claim_id": "clm_01HK7CLM0DEF1GHIJ2KLM3NOPQ"
}The dashboard user explicitly approves or rejects the claim made by
the MCP client. On approve, the server creates the sessions row
and stores session_id + api_key_hash in the device_code's
claim_metadata; the :complete endpoint reads these back rather than
re-creating a session. On reject, the device_code transitions to
denied and the event is audit-logged. The verifying → connected
(i.e. claimed → approved) transition is the only path other
than :confirm:approve that resolves the claim — server-side
Storm-2372 mitigation (SEC-04). PROTO-04.
| id required | string Example: ag_01H7abc12345 |
| claim_id required | string |
| action required | string Enum: "approve" "reject" |
{- "claim_id": "clm_01HK7CLM0DEF1GHIJ2KLM3NOPQ",
- "action": "approve"
}{- "state": "approved",
- "confirmed_at": "2026-06-10T18:01:30Z"
}Polled by the MCP client / CLI after :claim. The claim_id is the
bearer credential (no Authorization header). Returns
{session_id, agent_id, api_key, access_token, expires_at, server_now}
ONCE — the plaintext API key is never re-derivable. The session_id
is the row created during :confirm:approve; :complete does not
create a session. The access_token is a fresh JWT with a 1-hour TTL
sourced from auth.AgentIssuer.Issue. PROTO-05 / D-02.
| claim_id required | string |
{- "claim_id": "clm_01HK7CLM0DEF1GHIJ2KLM3NOPQ"
}{- "session_id": "ses_01HK7SES0DEF1GHIJ2KLM3NOPQ",
- "agent_id": "ag_01H7abc12345",
- "api_key": "ak_live_AAAA1234567890abcdefghijklmnop",
- "access_token": "eyJhbGc...",
- "expires_at": "2026-06-10T19:01:30Z",
- "server_now": "2026-06-10T18:01:30Z"
}Updates sessions.last_seen_at = NOW() (database clock; Pitfall 7).
Per-session rate limit of 1/min (SEC-10, Pitfall 6). PROTO-06.
| id required | string Example: ses_01HK7SES0DEF1GHIJ2KLM3NOPQ |
{- "last_seen_at": "2026-06-10T18:05:00Z",
- "next_allowed_at": "2026-06-10T18:06:00Z",
- "server_now": "2026-06-10T18:05:00Z"
}OPS-05. Unauthenticated lookup used by the /connect/{code} frontend deeplink to resolve the agent_id before navigating to /agents/{agent_id}/connect. Returns ONLY agent_id; never tenant_id, client_info, or claim_metadata. Codes in denied or expired state return 404 (indistinguishable from never-existed) to defeat enumeration.
| code required | string^[BCDFGHJKMNPQRSTVWXYZ23456789]{4}-[BCDFGHJKM... |
{- "agent_id": "string"
}| id required | string |
{- "keys": [
- {
- "id": "key_01H7abc12345",
- "prefix": "ak_live_AbCdEfGh",
- "scopes": [
- "send",
- "receive"
], - "last_used_at": "2026-05-28T14:55:00Z",
- "created_at": "2026-05-01T10:00:00Z"
}
]
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| scopes required | Array of strings non-empty Items Enum: "send" "receive" "memory:read" "memory:write" "admin" "channel:read" "channel:write" "interests:read" "interests:write" |
| expires_at | string or null <date-time> |
{- "scopes": [
- "send",
- "receive",
- "memory:read",
- "memory:write"
], - "expires_at": "2027-05-28T00:00:00Z"
}{- "id": "key_01H7abc12345",
- "prefix": "ak_live_AbCdEfGh",
- "key": "ak_live_AbCdEfGhIjKlMnOpQrStUvWxYz0123456",
- "scopes": [
- "send",
- "receive",
- "memory:read",
- "memory:write"
], - "expires_at": "2027-05-28T00:00:00Z",
- "created_at": "2026-05-28T15:00:00Z"
}| id required | string |
{- "interests": [
- {
- "id": "int_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "event_type": "com.example.order.*",
- "filter_expression": "amount_cents > 10000",
- "priority": 10,
- "enrichment_depth": "light",
- "active": true,
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z"
}
]
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| event_type required | string non-empty |
| filter_expression | string or null |
| priority required | integer >= 0 |
| enrichment_depth required | string Enum: "none" "light" "full" |
{- "event_type": "com.example.order.*",
- "filter_expression": "amount_cents > 10000",
- "priority": 10,
- "enrichment_depth": "light"
}{- "id": "int_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "event_type": "com.example.order.*",
- "filter_expression": "amount_cents > 10000",
- "priority": 10,
- "enrichment_depth": "light",
- "active": true,
- "created_at": "2026-05-28T15:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z"
}| id required | string |
| interest_id required | string |
{- "error": {
- "code": "validation_error",
- "message": "name is required",
- "request_id": "req_01H7abc12345",
- "details": {
- "field": "name"
}
}
}Returns all channels in the caller's tenant scope. Use offset/limit for pagination.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 200 ] Default: 50 |
{- "channels": [
- {
- "id": "ch_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "ops-alerts",
- "description": "Operational alerts for production",
- "type": "open",
- "created_by_type": "agent",
- "created_by_id": "ag_01H7abc12345",
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z"
}
], - "total": 1
}Creates a new channel in the caller's tenant. Channel names must be unique within the tenant.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| name required | string non-empty |
| description | string or null |
| type required | string Enum: "open" "private" "direct" |
{- "name": "ops-alerts",
- "description": "Operational alerts for production",
- "type": "open"
}{- "id": "ch_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "ops-alerts",
- "description": "Operational alerts for production",
- "type": "open",
- "created_by_type": "agent",
- "created_by_id": "ag_01H7abc12345",
- "created_at": "2026-05-28T15:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z"
}{- "id": "ch_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "name": "ops-alerts",
- "description": "Operational alerts for production",
- "type": "open",
- "created_by_type": "agent",
- "created_by_id": "ag_01H7abc12345",
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z"
}{- "members": [
- {
- "id": "mbr_01H7abc12345",
- "channel_id": "ch_01H7abc12345",
- "participant_type": "agent",
- "participant_id": "ag_01H7abc12345",
- "role": "owner",
- "joined_at": "2026-05-01T10:00:00Z"
}
]
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| participant_type required | string Enum: "agent" "org_admin" |
| participant_id required | string |
| role | string Default: "member" Enum: "owner" "admin" "member" |
{- "participant_type": "agent",
- "participant_id": "ag_01H7def67890",
- "role": "member"
}{- "id": "mbr_01H7def67890",
- "channel_id": "ch_01H7abc12345",
- "participant_type": "agent",
- "participant_id": "ag_01H7def67890",
- "role": "member",
- "joined_at": "2026-05-28T15:00:00Z"
}| id required | string |
| participant_type required | string Enum: "agent" "org_admin" |
| participant_id required | string |
{- "error": {
- "code": "validation_error",
- "message": "name is required",
- "request_id": "req_01H7abc12345",
- "details": {
- "field": "name"
}
}
}| id required | string |
{- "feeds": [
- {
- "id": "feed_01H7abc12345",
- "channel_id": "ch_01H7abc12345",
- "event_type_glob": "com.example.order.*",
- "filter_expression": "amount_cents > 10000",
- "active": true,
- "created_at": "2026-05-01T10:00:00Z"
}
]
}| id required | string |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| event_type_glob required | string non-empty |
| filter_expression | string or null |
{- "event_type_glob": "com.example.order.*",
- "filter_expression": "amount_cents > 10000"
}{- "id": "feed_01H7abc12345",
- "channel_id": "ch_01H7abc12345",
- "event_type_glob": "com.example.order.*",
- "filter_expression": "amount_cents > 10000",
- "active": true,
- "created_at": "2026-05-28T15:00:00Z"
}Human messaging facade. All routes accept EITHER an org_session httponly
cookie (browser / SSE) OR Authorization: Bearer <jwt> (agent / SDK / CLI).
Both auth paths yield an equivalent CallerContext used by all handlers.
Publishes a message to the specified channel. The caller must be a member
of the channel. Produces a CloudEvents 1.0 envelope on angareion.raw
with actortype/actorid extensions from the authenticated caller identity.
Mention IDs are validated as channel members in the same transaction.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| channel required | string Channel to post to. Accepts |
| text required | string [ 1 .. 10000 ] characters Message text content. |
Array of objects (MentionEntry) <= 50 items Participants to mention. Must be channel members.
Non-member mentions return 422 with |
{- "channel": "#general",
- "text": "Hello, team!"
}{- "event_id": "evt_01H7abc12345",
- "status": "accepted",
- "channel_id": "ch_01H7abc12345",
- "delivery_id": "550e8400-e29b-41d4-a716-446655440000"
}Finds or creates the direct channel between the authenticated caller and
the target participant, returning the channel_id. No message is inserted.
Idempotent: repeated calls with the same peer return the same channel ID.
Use this to resolve the DM channel before the first message is typed.
| participant_ref required | string^(agent|org_admin):[a-zA-Z0-9_-]+$ Example: agent:ag_01H7abc12345 Target participant reference. Format: |
{- "channel_id": "ch_01H7abc12345"
}Atomically finds or creates a direct channel between the caller and the
target participant, then sends a message. For self-DM (participant_ref ==
caller's own ID), creates a type=self channel. For 2-party DM, creates a
type=direct channel named direct:{sortedA}:{sortedB}. 3+ participants
return 422.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| participant_ref required | string^(agent|org_admin):[a-zA-Z0-9_-]+$ Example: agent:ag_01H7abc12345 Target participant reference. Format: |
| text required | string [ 1 .. 10000 ] characters |
{- "text": "Hey, quick question"
}{- "event_id": "evt_01H7abc12345",
- "status": "accepted",
- "channel_id": "ch_01H7abc12345",
- "delivery_id": "550e8400-e29b-41d4-a716-446655440000"
}Returns messages in the channel, most recent first. The caller must be a
channel member — non-members receive 404 (no existence leak). Uses keyset
pagination via an opaque cursor string encoding (delivery_id, created_at).
| id required | string Example: ch_01H7abc12345 |
| cursor | string Opaque cursor from a previous response |
| limit | integer [ 1 .. 200 ] Default: 50 |
{- "messages": [
- {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "channel_id": "ch_01H7abc12345",
- "sender_type": "agent",
- "sender_id": "ag_01H7abc12345",
- "text": "Hello, team!",
- "mentions": [
- {
- "type": "agent",
- "id": "ag_01H7abc12345"
}
], - "created_at": "2026-06-08T12:00:00Z"
}
], - "has_more": true,
- "next_cursor": "eyJkZWxpdmVyeV9pZCI6Ii4uLiJ9"
}Returns all channels where the caller is a participant, sorted by last
message time descending. Each item includes a last_message preview and
unread_count derived from message_read_state.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| unread_only | boolean When true, client-side filter returns only items with unread_count > 0. |
{- "items": [
- {
- "channel_id": "ch_01H7abc12345",
- "channel_name": "ops-alerts",
- "channel_type": "open",
- "unread_count": 3,
- "last_message": {
- "id": "550e8400-e29b-41d4-a716-446655440000",
- "channel_id": "ch_01H7abc12345",
- "sender_type": "agent",
- "sender_id": "ag_01H7abc12345",
- "text": "Hello, team!",
- "mentions": [
- {
- "type": "agent",
- "id": "ag_01H7abc12345"
}
], - "created_at": "2026-06-08T12:00:00Z"
}
}
]
}Monotone-forward only: if the specified delivery_id is older than the
current read pointer, this is a no-op. On success emits an
inbox.read.advanced SSE event to all sessions of the same caller.
Tool errors are returned as {error_code, message, hint?} so MCP clients can surface user-friendly retry guidance (e.g., for invalid_mentions).
| id required | string <uuid> Example: 550e8400-e29b-41d4-a716-446655440000 delivery_id (UUID) of the message to mark read |
{- "error": {
- "code": "validation_error",
- "message": "name is required",
- "request_id": "req_01H7abc12345",
- "details": {
- "field": "name"
}
}
}Returns members of the specified channel whose name or ID matches q.
Scoped to channel members only — never tenant-wide. Used by mention pickers
in chat UI and SDK tooling.
| id required | string Example: ch_01H7abc12345 |
| q | string <= 100 characters Prefix search string for member name/ID |
{- "candidates": [
- {
- "type": "agent",
- "id": "string",
- "display_name": "string"
}
]
}| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 200 ] Default: 50 |
| type | string Enum: "episodic" "semantic" "procedural" "entity" "reflection" "reasoning" |
| scope | string Enum: "agent" "team" "institutional" |
| search | string Substring match against title and content (handler-side LIKE). |
{- "memories": [
- {
- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "agent",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.85,
- "importance": 0.6,
- "metadata": { },
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z",
- "accessed_at": "2026-05-28T14:00:00Z"
}
], - "total": 1
}| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| title required | string non-empty |
| content required | string [ 1 .. 32768 ] characters |
| type required | string Enum: "episodic" "semantic" "procedural" "entity" "reflection" "reasoning" |
| scope required | string Enum: "agent" "team" "institutional" |
| team_id | string or null Required if scope=team and not in JWT context. |
| project_id | string or null |
| confidence | number [ 0 .. 1 ] |
| importance | number [ 0 .. 1 ] |
object | |
| source_type | string or null |
| source_id | string or null |
| source_url | string or null <uri> |
| source_event_id | string or null |
| freshness_date | string or null <date-time> |
| expires_at | string or null <date-time> |
{- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "type": "semantic",
- "scope": "agent",
- "confidence": 0.85,
- "importance": 0.6,
- "source_event_id": "evt_01H7abc12345"
}{- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "agent",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.85,
- "importance": 0.6,
- "metadata": { },
- "source_event_id": "evt_01H7abc12345",
- "created_at": "2026-05-28T15:00:00Z",
- "updated_at": "2026-05-28T15:00:00Z",
- "accessed_at": "2026-05-28T15:00:00Z"
}Returns a read-only, tenant-scoped health snapshot of the memory plane: duplicate_count (open merge proposals), orphan_count (memories with no incoming MENTIONS edge), stale_count (below the decay/archive floor), embed_gap_count (active rows missing a vector), reflection_count, and a decay_distribution histogram over memory importance. Read-only — it never blocks concurrent writes.
{- "duplicate_count": 3,
- "orphan_count": 4,
- "stale_count": 2,
- "embed_gap_count": 5,
- "reflection_count": 6,
- "decay_distribution": {
- "1": 5,
- "10": 4
}
}| id required | string <uuid> Example: mem_01H7abc12345 |
{- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "agent",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.85,
- "importance": 0.6,
- "metadata": { },
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z",
- "accessed_at": "2026-05-28T14:00:00Z"
}| id required | string <uuid> |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| title | string |
| content | string <= 32768 characters |
| confidence | number [ 0 .. 1 ] |
| importance | number [ 0 .. 1 ] |
object | |
| expires_at | string or null <date-time> |
| freshness_date | string or null <date-time> |
{- "confidence": 0.9,
- "importance": 0.7
}{- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "agent",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.9,
- "importance": 0.7,
- "metadata": { },
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-28T15:05:00Z",
- "accessed_at": "2026-05-28T15:05:00Z"
}Per PRD-02. Performs scope-aware semantic search across the caller's accessible memories (agent → team → institutional ascending). Results are ranked by composite score combining semantic similarity, confidence, and recency. The exact ranking signals may evolve; the request/response keys documented here are stable.
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| query required | string non-empty Natural-language search query. |
| scope | string Enum: "agent" "team" "institutional" Limit results to a specific scope. Default searches all accessible scopes. |
| memory_types | Array of strings Items Enum: "episodic" "semantic" "procedural" "entity" "reflection" "reasoning" Filter results by memory type (array form; replaces single-type filter). |
| limit | integer [ 1 .. 100 ] Default: 10 |
| min_score | number [ 0 .. 1 ] Minimum similarity score (0..1) for returned memories. |
{- "query": "customer support preferences",
- "scope": "agent",
- "memory_types": [
- "semantic"
], - "limit": 10,
- "min_score": 0.5
}{- "results": [
- {
- "memory": {
- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "agent",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.85,
- "importance": 0.6,
- "metadata": { },
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-01T10:00:00Z",
- "accessed_at": "2026-05-28T14:00:00Z"
}, - "score": 0.92
}
], - "total": 1
}Creates a directed relationship from the memory in the path (id) to
another memory (target_memory_id). The server enforces a closed enum
for relationship_type — values outside the enum are rejected with
HTTP 400 validation_error. Backed by MemoryHandler.CreateRelationship
in api/internal/handlers/memory.go.
Valid relationship_type values:
strength and metadata are optional. If strength is omitted the
server applies a default; if metadata is omitted it is stored as null.
| id required | string Example: mem_01H7abc12345 Source memory id. |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| target_memory_id required | string <uuid> Target memory id (UUID). The source memory id is taken from the path parameter. |
| relationship_type required | string Enum: "caused_by" "related_to" "contradicts" "supports" "derived_from" "part_of" Closed enum. The server validates this value against the list below and rejects any other value with HTTP 400 validation_error. |
| strength | number <double> [ 0 .. 1 ] Optional edge weight in [0, 1]. Server applies a default if omitted
(handler stores |
object or null Optional freeform JSON object attached to the edge. May be omitted (sent as null) — the server stores either the object or no metadata. |
{- "target_memory_id": "mem_neighbor1234",
- "relationship_type": "derived_from",
- "strength": 0.85,
- "metadata": {
- "confidence": 0.9,
- "source": "manual"
}
}{- "id": "rel_01H7abc12345",
- "tenant_id": "ten_01H7abc12345",
- "source_memory_id": "mem_01H7abc12345",
- "target_memory_id": "mem_neighbor1234",
- "relationship_type": "derived_from",
- "strength": 0.85,
- "metadata": {
- "confidence": 0.9,
- "source": "manual"
}, - "created_at": "2026-06-01T12:34:56Z"
}Performs a breadth-first traversal of the memory graph starting at id
and returns each reached memory along with its hop depth and the
cumulative product of edge strengths along the traversal path.
Backed by MemoryHandler.ListRelationships in
api/internal/handlers/memory.go. Returns the items the caller's tenant
is permitted to see; tenant scoping is enforced server-side.
The count field is the number of items returned, not a paginated total.
| id required | string <uuid> Example: mem_01H7abc12345 Source memory id (UUID). |
| depth | integer [ 1 .. 3 ] Default: 1 Example: depth=1 Number of relationship hops to traverse. Server clamps to [1, 3]; values <= 0 default to 1, values > 3 are capped at 3. |
{- "relationships": [
- {
- "memory_id": "mem_neighbor1234",
- "depth": 1,
- "strength_product": 0.85
}, - {
- "memory_id": "mem_far_neighb56",
- "depth": 2,
- "strength_product": 0.42
}
], - "count": 2
}Removes a single directed relationship by its server-assigned id.
Backed by MemoryHandler.DeleteRelationship in
api/internal/handlers/memory.go. Tenant scoping is enforced
server-side; the caller must be authenticated as an agent in the
owning tenant.
| id required | string <uuid> Example: mem_01H7abc12345 Source memory id (UUID). |
| rel_id required | string <uuid> Example: rel_01H7abc12345 Relationship id returned by createMemoryRelationship. |
{- "error": {
- "code": "validation_error",
- "message": "invalid relationship ID format",
- "request_id": "req_01H7abc12345"
}
}Returns the Citation nodes linked to this memory via CITES edges in
Neo4j (Phase 03 read path). Citations are written by the extraction
pipeline when source documents are ingested (Phase 02 EXT-01).
Returns an empty citations array (with HTTP 200) for memories created
manually without a source document — this is NOT an error condition.
Backed by SearchHandler.Citations in api/internal/search/handler.go,
which calls the memory-bridge gRPC GetMemoryCitations RPC with a 2s
deadline and falls back to Postgres lineage if the bridge is unavailable.
| id required | string <uuid> Example: mem_01H7abc12345 Memory UUID. |
{- "citations": [
- {
- "document_id": "",
- "document_name": "Angareion Architecture Guide",
- "page": 4,
- "snippet": "The sensing pipeline processes CloudEvents...",
- "source_id": "",
- "confidence": 0.85,
- "version": 1,
- "freshness": {
- "stale": false,
- "stale_since": null
}
}
], - "total": 1
}| id required | string <uuid> |
| Idempotency-Key | string <= 255 characters Example: idem_01H7abc12345 Client-supplied key making POST/PATCH operations idempotent (per PRD-03 §4.1). Repeated requests with the same key return the original response without executing again. Server retains the key for at least 24 hours. |
| scope required | string Enum: "team" "institutional" Target scope. Cannot be "agent" (only promotion supported). |
| team_id | string or null Required when scope=team. |
{- "scope": "team",
- "team_id": "team_01H7abc12345"
}{- "id": "mem_01H7abc12345",
- "tenant_id": "tn_01H7abc12345",
- "agent_id": "ag_01H7abc12345",
- "scope": "team",
- "team_id": "team_01H7abc12345",
- "type": "semantic",
- "status": "active",
- "title": "Customer prefers async support",
- "content": "Customer cs_42 historically responds to email within 6h...",
- "confidence": 0.85,
- "importance": 0.6,
- "metadata": { },
- "created_at": "2026-05-01T10:00:00Z",
- "updated_at": "2026-05-28T15:10:00Z",
- "accessed_at": "2026-05-28T15:10:00Z"
}| memory_a_id required | string <uuid> First memory in the proposed merge (must exist in the caller's tenant). |
| memory_b_id required | string <uuid> Second memory in the proposed merge (must differ from memory_a_id). |
| resolution_note | string or null Optional free-text note explaining the proposal. |
{- "memory_a_id": "mem_01H7abc12345",
- "memory_b_id": "mem_01H7def67890",
- "resolution_note": "Both describe the same customer preference"
}{- "id": "string",
- "tenant_id": "string",
- "proposer_user_id": "string",
- "memory_a_id": "string",
- "memory_b_id": "string",
- "surviving_memory_id": "string",
- "status": "open",
- "proposed_at": "2019-08-24T14:15:22Z",
- "resolved_at": "2019-08-24T14:15:22Z",
- "resolved_by_user_id": "string",
- "resolution_note": "string"
}Resolves an open merge proposal, keeping surviving_memory_id and collapsing the other. Org-admin only — enforced at the REST layer (D-08). A non-admin caller receives 403, surfaced here as a tool error.
| id required | string <uuid> |
| surviving_memory_id required | string <uuid> The memory to keep. Must match memory_a_id or memory_b_id. |
| resolution_note | string or null Optional free-text note explaining the resolution. |
| merged_content | string or null Optional merged text to apply to the surviving memory. |
{- "surviving_memory_id": "mem_01H7abc12345",
- "resolution_note": "Kept the more complete record"
}{- "id": "string",
- "tenant_id": "string",
- "proposer_user_id": "string",
- "memory_a_id": "string",
- "memory_b_id": "string",
- "surviving_memory_id": "string",
- "status": "open",
- "proposed_at": "2019-08-24T14:15:22Z",
- "resolved_at": "2019-08-24T14:15:22Z",
- "resolved_by_user_id": "string",
- "resolution_note": "string"
}Rejects an open merge proposal without collapsing either memory. Org-admin only — enforced at the REST layer (D-08). A non-admin caller receives 403, surfaced here as a tool error.
| id required | string <uuid> |
| resolution_note | string or null Optional free-text note explaining the rejection. |
{- "resolution_note": "Not actually duplicates"
}{- "id": "string",
- "tenant_id": "string",
- "proposer_user_id": "string",
- "memory_a_id": "string",
- "memory_b_id": "string",
- "surviving_memory_id": "string",
- "status": "open",
- "proposed_at": "2019-08-24T14:15:22Z",
- "resolved_at": "2019-08-24T14:15:22Z",
- "resolved_by_user_id": "string",
- "resolution_note": "string"
}Phase 04 R5. Reversible self-curation: sets the memory's invalid_at
(invalidate-never-delete — the row is preserved) so it stops surfacing in
current memory_search, but can be recovered within the reversibility
window. This is NOT a hard delete (hard-delete/RTBF is a separate
admin-gated path). Backed by MemoryHandler.Forget in
api/internal/handlers/memory.go via the RetireOrMerger OpForget
primitive.
Idempotent: re-calling on an already-invalidated memory returns HTTP 200
with invalid_at unchanged (no error, no second mutation). Winner-safe:
forgetting a contradiction winner never resurrects the losers it
invalidated. Requires memory_id (UUID).
| id required | string <uuid> Memory UUID. |
{- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "forgotten": true,
- "invalid_at": "2019-08-24T14:15:22Z"
}Phase 04 R6. Read-only provenance inspection. Returns the memory's
merge_lineage rows (which memories were merged into or away from it) plus
the SUPERSEDES/DERIVED_FROM graph trace from the memory-bridge. A
freshly-created, never-merged/never-superseded memory returns HTTP 200 with
an empty ancestor set (NOT 404) — an empty lineage is a valid answer, not an
error. Backed by MemoryHandler.ExplainLineage in
api/internal/handlers/memory.go. Requires memory_id (UUID).
| id required | string <uuid> Memory UUID. |
{- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "merge_lineage": [
- {
- "loser_memory_id": "1d90f1b2-586d-498a-b01e-f548d5d0d176",
- "winner_memory_id": "77ec9f0a-4ef4-47de-97c5-19fd82736c2b",
- "origin": "string"
}
], - "superseded": true,
- "supersedes_target": "aabcdb89-2335-4e7a-91d6-b87d926d6653"
}Upload a document (PDF, DOCX, PPTX, XLSX, HTML, TXT, MD) to the agent's
knowledge base. file_content must be standard base64-encoded bytes.
Returns {accepted: true, document_id, status: "pending"} on 202.
Use angareion_list_documents to poll for indexing status.
Returns {accepted: false, error_code: "permission_denied"} when the
agent lacks knowledge:write.
Phase 02 D-09: optional sensitivity and audience fields; defaults
applied server-side when absent.
| file_content required | string Standard base64-encoded bytes of the document. |
| filename required | string Original filename including extension. |
| mime_type required | string Enum: "application/pdf" "application/vnd.openxmlformats-officedocument.wordprocessingml.document" "application/vnd.openxmlformats-officedocument.presentationml.presentation" "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" "text/html" "text/markdown" "text/plain" MIME type of the document. |
| sensitivity | string Enum: "public" "internal" "confidential" "restricted" Optional sensitivity label. Server defaults to "internal" when absent. |
object Optional 4-dimension audience tag JSONB. Format: {regions:[...], industries:[...], product_lines:[...], segments:[...]}. Server defaults to {} when absent. |
{- "file_content": "JVBERi0xLjQK...",
- "filename": "architecture-guide.pdf",
- "mime_type": "application/pdf"
}{- "accepted": true,
- "document_id": "doc_01H7abc12345",
- "status": "pending"
}Flat list across all ingestion sources for the agent's tenant.
Optional offset (default 0) and limit (default 20, max 100) for pagination.
Returns {documents: [...], total}. Each document has document_id, filename,
mime_type, status (pending|indexed|failed), ingested_at,
ingestion_source_id, and ingestion_source_name.
| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 100 ] Default: 20 |
{- "documents": [
- {
- "document_id": "doc_01H7abc12345",
- "filename": "architecture-guide.pdf",
- "mime_type": "application/pdf",
- "status": "indexed",
- "ingested_at": "2026-05-01T10:00:00Z",
- "ingestion_source_id": "src_01H7abc12345",
- "ingestion_source_name": "default"
}
], - "total": 1
}Returns parsed chunks for the document ordered by chunk_index ascending.
Requires source_id (UUID) and document_id (UUID).
Optional offset (default 0) and limit (default 20, max 100).
Each chunk has chunk_id, document_id, page_number, char_offset,
char_length, text, and created_at.
Use this to inspect what was extracted before searching or citing memories.
| source_id required | string <uuid> Ingestion source UUID. |
| document_id required | string <uuid> Document UUID. |
| offset | integer >= 0 Default: 0 |
| limit | integer [ 1 .. 100 ] Default: 20 |
{- "chunks": [
- {
- "chunk_id": "55e808e1-8ddc-49f1-92c9-6bbdcdff1c83",
- "document_id": "b792e8ae-2cb4-4209-85b9-32be4c2fcdd6",
- "page_number": 0,
- "char_offset": 0,
- "char_length": 0,
- "chunk_index": 0,
- "text": "string",
- "created_at": "2019-08-24T14:15:22Z"
}
], - "total": 0
}Permanently deletes a document and all its chunks from the agent's
knowledge base. Requires source_id (UUID) and document_id (UUID).
Returns {deleted: true, document_id} on success.
Returns {deleted: false, error_code: "not_found"} when the document
does not exist (structured result, NOT an HTTP 404 error).
Chunks are removed from Postgres and the corresponding Neo4j Chunk nodes
are deleted by the gateway.
| source_id required | string <uuid> Ingestion source UUID. |
| document_id required | string <uuid> Document UUID. |
{- "deleted": true,
- "document_id": "doc_01H7abc12345"
}Returns every Document/Chunk the memory was derived from.
Requires memory_id (UUID). Each citation has document_id,
document_name, page, snippet, source_uri, confidence,
version, and freshness{stale, stale_since}.
Use this to verify provenance and surface source attribution to the user.
Returns an empty citations array (HTTP 200) for memories created
without a source document — this is NOT an error condition.
| id required | string <uuid> Memory UUID. |
{- "citations": [
- {
- "document_id": "",
- "document_name": "Angareion Architecture Guide",
- "page": 4,
- "snippet": "The sensing pipeline processes CloudEvents...",
- "source_id": "",
- "confidence": 0.85,
- "version": 1,
- "freshness": {
- "stale": false,
- "stale_since": null
}
}
], - "total": 2
}Returns the latest specialist preamble (system_prompt, memory_search_directives,
output_format_rules, version_num) for the bound agent's tenant.
Returns a structured {error: {code: "not_found"}} when no such specialist
exists; {error: {code: "unavailable"}} after 3 retries on transport failure.
Errors are returned as structured results — NOT HTTP error codes — so MCP
clients can surface user-friendly retry guidance.
| name required | string Specialist name (slug). |
{- "name": "pmm-intel",
- "version_num": 3,
- "system_prompt": "You are a PMM intelligence assistant...",
- "description": "Surfaces market intelligence for PMMs"
}Returns all specialists ordered alphabetically by the server (do not re-sort
on the client). Each specialist has name, version_num, description,
and updated_at.
{- "specialists": [
- {
- "name": "pmm-intel",
- "version_num": 3,
- "description": "Surfaces market intelligence for PMMs",
- "updated_at": "2026-05-01T10:00:00Z"
}
]
}Removes the server-side session record for a (agent, specialist) pair. This is observability bookkeeping only — it does NOT remove the specialist's preamble from the current conversation context; those instructions are already in the conversation and keep applying. To stop following a specialist, load a different one (its reset clause supersedes the previous preamble) or start a fresh session. Idempotent — returns success even if no session was recorded. Phase 04.5 D-17 / SPEC R14.
| name required | string Specialist name (slug). |
{- "unloaded": true,
- "name": "pmm-intel"
}Retrieves memory context (excluding sensitivity=confidential per SPEC P1),
calls the tenant's LLM with prompt caching on the shared prefix, and returns
a draft text plus cited memory IDs.
Required: template_id (workflow template slug, v1: "launch-a-feature"),
run_id (UUID identifying this workflow run — same run_id twice returns
duplicate_run error).
Optional: context_memory_ids ([]string of memory UUIDs to consider),
launch_brief_text (description of the feature being launched),
prompt_id (reserved for future recipes; leave empty for v1).
On SPEC R6 duplicate_run, returns error code=duplicate_run.
| template_id required | string Workflow template slug (v1 value: "launch-a-feature"). |
| run_id required | string <uuid> UUID identifying this workflow run. Submitting the same run_id twice while a compose is in flight returns a 409 duplicate_run response. |
| context_memory_ids | Array of strings <uuid> [ items <uuid > ] Optional list of memory UUIDs to include as additional context. |
| launch_brief_text | string Optional free-text description of the feature or topic being composed. Used as part of the prompt context. |
| prompt_id | string Reserved for future recipe variants; leave empty for v1. |
{- "template_id": "launch-a-feature",
- "run_id": "550e8400-e29b-41d4-a716-446655440000",
- "launch_brief_text": "Announcing the new analytics dashboard for PMMs"
}{- "draft_text": "We are thrilled to announce...",
- "cited_memory_ids": [
- "mem_01H7abc12345"
]
}Cross-LLM session handoff — named, agent-scoped snapshots of working state saved in one LLM client and resumed in another. Every route here is agent-scoped and fails closed: a caller without an agent identity in its JWT receives 401, including an org-user token.
Persists a named snapshot of working state for the calling agent.
Saving to a name that already exists overwrites the prior payload for
that name in place and increments version_num; the prior payload is
not retained.
Extraction is client-side: the gateway never receives a transcript, so
the payload supplied here is everything a resuming session will have.
tenant_id and agent_id are read from the JWT and cannot be supplied
in the body. Unknown top-level keys are rejected with 400.
| name required | string Short, human-typeable, and reusable across the life of the work. Saving to an existing name overwrites that name's payload in place. |
| summary | string One-line description of what this working state covers. |
object The structured working state. The properties below are the KNOWN fields — they are validated for renderability, and unrecognised fields are preserved untouched rather than rejected, so client-side extraction can add fields without a server release.
| |
Array of objects Memory IDs already known to be relevant to this work. Each entry is an OBJECT, not a bare UUID: the server snapshots each cited memory's title at save time and stores it alongside the ID so the two cannot drift apart. A client-supplied A citation whose memory does not resolve is stored with an empty title and the save still succeeds. | |
| artifacts | Array of strings File paths, URLs, and branch names touched by this work. Stored as supplied and never validated for inner structure, so a client may send richer entries than the documented string form. |
| origin_client | string Identifier of the surface that saved this handoff, for example
|
| scope | string Default: "agent" Value: "agent"
|
{- "name": "q3-pricing",
- "summary": "Reworking the Q3 pricing page copy and the tier table",
- "payload": {
- "completed": [
- "Rewrote the hero section"
], - "remaining": [
- "Tier comparison table still uses the old tier names"
], - "blockers": [
- "Legal has not signed off on the enterprise wording yet"
], - "decisions": [
- {
- "title": "Dropped the enterprise tier from the table",
- "rationale": "Sales asked for it to stay, but the table becomes unreadable at four columns on mobile and enterprise is quote-only anyway."
}
], - "next_action": "Update the tier table to the new names",
- "context_notes": "Copy lives in frontend/apps/app/src/pricing/"
}, - "citations": [
- {
- "memory_id": "550e8400-e29b-41d4-a716-446655440000"
}
], - "artifacts": [
- "frontend/apps/app/src/pricing/hero.tsx",
], - "origin_client": "claude-code",
- "scope": "agent"
}{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "summary": "string",
- "payload": { },
- "citations": [
- {
- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "title": "string"
}
], - "artifacts": [
- "string"
], - "origin_client": "string",
- "scope": "agent",
- "version_num": 0,
- "pinned_at": "2019-08-24T14:15:22Z",
- "last_resumed_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Returns the calling agent's own handoffs, most-recently-saved first.
With q absent this is a plain listing. With q present the same
response shape is returned, narrowed by a merged keyword and semantic
match over saved names and summaries.
An empty array is a normal, successful result: it means this agent has no saved handoffs, not that anything failed.
| q | string Optional free-text term. When present, results are narrowed by a merged keyword and semantic match instead of being a plain listing. |
| limit | integer [ 1 .. 200 ] Default: 50 |
| offset | integer >= 0 Default: 0 Applies to the plain listing only. The searched branch is bounded by
|
[ ]Resolves a handoff and returns its rendered brief. Resolution tries an exact name match first, then an ID match, then a semantic match over saved names and summaries.
This operation has TWO distinct 200 shapes, told apart by the response
Content-Type header:
| Content-Type | Meaning |
|---|---|
text/markdown; charset=utf-8 |
A unique match. The body is the brief itself, and the handoff was resumed — its last-resumed timestamp is stamped and its expiry reset to a full TTL. |
application/json |
Ambiguous. The body is a candidate list and NOTHING was resumed. Present the choices to the user and resume the chosen one by name or id. |
A literal resume is never treated as a handoff name: this is a fixed
route segment, so a saved handoff named resume is reached by ID.
| query required | string An exact handoff name, a handoff UUID, or a free-text description of the work in your own words when the exact name is not known. |
{- "query": "q3-pricing"
}Scoped to the caller's tenant AND agent. Another agent's handoff is reported as 404, identical to one that does not exist, so IDs cannot be probed.
| id required | string <uuid> Handoff UUID. |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "summary": "string",
- "payload": { },
- "citations": [
- {
- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "title": "string"
}
], - "artifacts": [
- "string"
], - "origin_client": "string",
- "scope": "agent",
- "version_num": 0,
- "pinned_at": "2019-08-24T14:15:22Z",
- "last_resumed_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}Removes the handoff from the relational store and its vector point from the handoffs collection in the same request.
| id required | string <uuid> Handoff UUID. |
{- "error": {
- "code": "validation_error",
- "message": "name is required",
- "request_id": "req_01H7abc12345",
- "details": {
- "field": "name"
}
}
}Returns the deterministic Markdown brief — byte-for-byte the same bytes a successful resume returns. Exporting does NOT resume: no timestamp is stamped and no expiry is reset. The audit trail records this as an export, distinct from a resume.
| id required | string <uuid> Handoff UUID. |
{- "error": {
- "code": "validation_error",
- "message": "name is required",
- "request_id": "req_01H7abc12345",
- "details": {
- "field": "name"
}
}
}A pinned handoff survives the expiry reaper indefinitely until it is unpinned or deleted.
| id required | string <uuid> Handoff UUID. |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "summary": "string",
- "payload": { },
- "citations": [
- {
- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "title": "string"
}
], - "artifacts": [
- "string"
], - "origin_client": "string",
- "scope": "agent",
- "version_num": 0,
- "pinned_at": "2019-08-24T14:15:22Z",
- "last_resumed_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}| id required | string <uuid> Handoff UUID. |
{- "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08",
- "name": "string",
- "summary": "string",
- "payload": { },
- "citations": [
- {
- "memory_id": "125896f9-d205-40e5-a9a8-a3e0b2b9450e",
- "title": "string"
}
], - "artifacts": [
- "string"
], - "origin_client": "string",
- "scope": "agent",
- "version_num": 0,
- "pinned_at": "2019-08-24T14:15:22Z",
- "last_resumed_at": "2019-08-24T14:15:22Z",
- "expires_at": "2019-08-24T14:15:22Z",
- "created_at": "2019-08-24T14:15:22Z",
- "updated_at": "2019-08-24T14:15:22Z"
}