API contract inventory

This page is the post-baseline inventory for Holon's HTTP control-plane API. It complements the HTTP control plane reference by recording the route list, request parameters, response shapes, and contract gaps that still need stabilization before scripts and integrations can rely on the API long term.

Stability levels

LevelMeaning
Candidate stablePublicly useful and already documented or consumed by the CLI/TUI, but still needs snapshot tests or explicit schema guarantees.
ExperimentalAvailable in the current runtime but likely to change as the runtime model evolves.
CapabilityAddressed through a generated callback capability token. Do not expose or log tokens.
Internal/debugIntended for local diagnostics, compatibility aliases, or temporary integration paths.
GapMissing or underspecified API surface that should be designed before it is treated as stable.

Cross-cutting contracts

Transport and authentication

SurfaceCurrent behaviorStability
HTTP TCPRoutes are served by the Axum router in src/http.rs.Candidate stable
Unix socket client fallbackLocalClient tries the configured Unix socket first when present, then falls back to HTTP.Candidate stable for local clients; not documented as a general remote API
Bearer authWhen require_control_token is true, read routes and /control/* routes require Authorization: Bearer <token>.Candidate stable
Local modeWhen no control token is required, the server trusts the local process boundary.Candidate stable, but should be tied to explicit deployment guidance
Callback capability tokens/callbacks/*/:callback_token routes authenticate by resolving the token to an external trigger record.Capability

Ingress trust/auth boundaries

Ingress classRoutesAuth boundaryRuntime provenancePriority policyStability
Public enqueue/enqueue, /agents/:agent_id/enqueueBearer token in bearer mode; local process boundary in local mode.Accepts only channel or webhook origins. Caller-provided trust is rejected. Channel origins become untrusted external evidence; webhook origins become integration signals. Runtime-owned message kinds are rejected.Allows next, normal, and background; rejects interject.Candidate stable
Callback capability/callbacks/wake/:callback_token, /callbacks/enqueue/:callback_tokenCapability token in path must resolve to an active external trigger and match the route delivery mode. Full callback URLs are secrets.Admitted as an external-trigger capability and integration signal. Wake mode emits a runtime-owned inspection tick rather than trusting the payload as an operator instruction.Caller does not choose queue priority.Capability
Operator transport binding/control/agents/:agent_id/operator-bindingsControl auth. Delivery bearer token is validated on input and redacted in audit events.Records the binding used by remote operator ingress and delivery callbacks.N/A.Experimental
Operator transport ingress/control/agents/:agent_id/operator-ingressControl auth plus active binding, matching agent, matching actor, and matching provider when supplied.Enqueues a trusted operator prompt with operator_instruction authority and remote-operator transport metadata.Always interject.Experimental
Generic webhook compatibility/webhooks/generic/:agent_idBearer token in bearer mode; local process boundary in local mode.Converts the JSON payload into a trusted-integration webhook event from generic_webhook; route ignores caller-supplied provenance because the whole body is the payload.Always normal.Internal/debug

Common JSON and response behavior

Most successful responses are JSON, but Holon does not use one global success envelope. The stable policy is route-class based:

Route classSuccess policyRationale
DiscoveryEnvelope responses include ok: true plus discovery fields, except catalog-style discovery routes such as /models that intentionally return a direct record.Small discovery handshakes benefit from an explicit liveness marker; catalog records should remain directly consumable.
Read modelDirect records or arrays, without a synthetic ok field.Read routes expose existing runtime records and lists; adding wrapper envelopes would make CLI/TUI consumers unwrap data without adding state-transition information.
Control mutationsEnvelope responses include ok: true plus mutation outcome fields, unless the endpoint creates and returns a first-class record.Mutation callers need a clear admission/side-effect acknowledgement; record-creation routes can use the created record as the acknowledgement.
StreamsServer-Sent Events frames with JSON event data; no JSON success envelope after the stream opens.The successful response is the stream itself. Cursor and event-envelope details are covered by the event/SSE contract.
Capability callbacksEnvelope responses include ok: true plus callback delivery result fields.Callback callers need a compact acknowledgement while preserving the capability-specific delivery result.

Representative current examples:

Handler-produced control-plane errors use one shared JSON envelope:

{
  "ok": false,
  "error": "message",
  "code": "machine_readable_code",
  "hint": "optional operator guidance"
}

For those handler-produced errors, ok is always false; error is a human-readable message. code and hint are optional shared fields. Routes may add documented route-specific extension fields alongside the shared fields. Framework-level rejections produced before a handler runs, such as Axum extractor failures for malformed JSON or request bodies rejected by DefaultBodyLimit, are not yet normalized into this envelope.

Status-code mapping:

StatusClassCurrent mapping
400 Bad RequestValidationMalformed or unsupported request fields, empty required strings, invalid callback body, unsupported operator delivery auth.
403 ForbiddenAuthentication/authorizationMissing, malformed, or invalid bearer token; private agent access; invalid callback capability token; ingress policy rejection.
404 Not FoundMissing resource/cursorUnknown public agent, archived public agent, unknown compatibility route, or event cursor outside the replay window.
409 ConflictState conflictStopped agent, stale or missing current run for abort, duplicate skill install, active workspace detach conflict when surfaced as a conflict.
424 Failed DependencyDependency unavailableSkill manager is unavailable.
502 Bad GatewayUpstream failureRemote skill installer failed.
504 Gateway TimeoutUpstream timeoutRemote skill installer timed out.
503 Service UnavailableRuntime service unavailableRuntime service metadata is required but absent.
500 Internal Server ErrorInternal/runtime errorUnexpected runtime, storage, workspace, or handler errors.

Common route-specific error extensions currently include:

FieldUsed byMeaning
agent_idstopped-agent and no-current-run conflictsAgent related to the rejected operation.
after_seq / event_seqevent page/SSE cursor errorsCursor sequence that was not available in the replay window.
requested_run_id / current_run_idcurrent-run abort conflictsStale requested run and current active run.
skill_name, destination, manager, package, exit_status, stdout, stderr, timeout_secondsskill install errorsSkill-manager or remote-installer diagnostics.

Known stable error code values:

CodeStatusMeaning
agent_stopped409The target agent is stopped and must be started before prompts or wakes.
cursor_not_found404Requested event cursor is outside the retained replay window.
stale_run_id409Abort request named a run that is no longer current.
no_current_run409Abort request found no active run to abort.
skill_already_installed409Skill destination already exists.
skill_manager_unavailable424Required skill manager executable is unavailable.
remote_skill_install_failed502Remote skill installer exited unsuccessfully.
remote_skill_install_timeout504Remote skill installer exceeded its timeout.

src/client.rs decodes this envelope compatibly: it requires only error for display and preserves optional code and hint in LocalHttpError. Unknown extension fields are ignored by the client unless a caller inspects the raw response directly.

Endpoint inventory

Discovery and runtime

MethodPathInputsSuccess responseStabilityNotes
GET/Auth header when bearer mode is active.{ ok, default_agent }Candidate stableRoot discovery for the default agent id.
GET/handshakeAuth header when bearer mode is active.{ ok, protocol, auth, capabilities, runtime }Candidate stableProtocol version is currently holon-control / 1.
GET/modelsAuth header when bearer mode is active.{ available_models, model_availability }ExperimentalResponse has no ok envelope and returns model catalog/availability internals.
GET/control/runtime/readinessControl auth.RuntimeStatusResponse-like readiness payload.Candidate stableUsed by daemon/client readiness checks.
GET/control/runtime/statusControl auth.RuntimeStatusResponse with activity, startup surface, runtime config surface, and last failure.Candidate stableResponse can expose runtime config summaries; keep credential fields redacted.
POST/control/runtime/shutdownControl auth; body is ignored/empty JSON in client.RuntimeShutdownResponseExperimentalLifecycle control; should keep shutdown semantics explicit.

Agent read model

MethodPathInputsSuccess responseStabilityNotes
GET/agents/listAuth header when bearer mode is active.AgentListEntry[]Candidate stableLightweight list for selection/navigation.
GET/agents/:agent_id/statusPath agent_id; auth header when bearer mode is active.AgentSummaryCandidate stableMain read model for one agent.
GET/agents/:agent_id/statePath agent_id; auth header when bearer mode is active.AgentStateSnapshotExperimentalBroad bootstrap snapshot; includes agent, session, tasks, timers, work items, waiting intents, external triggers, notifications, workspace, execution.
GET/agents/:agent_id/briefsPath agent_id; query limit?.BriefRecord[]Candidate stableDefaults to 20.
GET/agents/:agent_id/tasksPath agent_id; query limit?.TaskRecord[]Candidate stable for list; DTO schema still broadDefaults to 50; active/recent task listing.
GET/agents/:agent_id/tasks/:task_idPath agent_id, task_id.TaskStatusSnapshotCandidate stable route; DTO schema still broadReturns a single task lifecycle snapshot.
GET/agents/:agent_id/tasks/:task_id/outputPath agent_id, task_id; query block?, timeout_ms?.TaskOutputResultCandidate stable route; DTO schema still broadReads bounded task output and can optionally wait for readiness.
GET/agents/:agent_id/timersPath agent_id; query limit?.TimerRecord[]Candidate stableDefaults to 50.
GET/agents/:agent_id/transcriptPath agent_id; query limit?.TranscriptEntry[]ExperimentalTranscript data can include provider/tool internals.
GET/agents/:agent_id/worktree-summaryPath agent_id.{ agent_id, summary }ExperimentalSummary shape follows managed worktree internals.
GET/agents/:agent_id/skillsPath agent_id; auth header when bearer mode is active.{ ok, agent_id, skills }ExperimentalSkills list shape follows local skill catalog records.

Default-agent aliases:

MethodPathAlias targetStability
GET/status/agents/:default/statusInternal/debug compatibility
GET/briefs/agents/:default/briefsInternal/debug compatibility
GET/state/agents/:default/stateInternal/debug compatibility
GET/transcript/agents/:default/transcriptInternal/debug compatibility
GET/worktree-summary/agents/:default/worktree-summaryInternal/debug compatibility

Events and streams

MethodPathInputsSuccess responseStabilityNotes
GET/agents/:agent_id/eventsPath agent_id; query before_seq?, after_seq?, limit?, order?, max_level?.EventsPageResponseCandidate stable route and envelopelimit defaults to the event window and is clamped. order is asc or desc. max_level filters event inclusion only.
GET/agents/:agent_id/events/streamPath agent_id; query after_seq?, limit?; Accept: text/event-stream recommended.SSE frames with JSON StreamEventEnvelope data.Candidate stable route and envelopeSSE id is event_seq; SSE event is the raw audit event kind.

Event page cursors are exclusive: after_seq returns records with higher event_seq, before_seq returns records with lower event_seq, and combining both returns after_seq < event_seq < before_seq. SSE streams start after the current tail when after_seq is omitted, replay from the current replay window when after_seq=0, and return 404 cursor_not_found before opening the stream when a non-zero after_seq is outside that replay window.

Stable StreamEventEnvelope fields:

{
  "id": "event-uuid",
  "event_seq": 42,
  "ts": "2026-05-24T00:00:00Z",
  "agent_id": "main",
  "type": "task_created",
  "provenance": { "authority_class": "operator_instruction", "task_id": "task-..." },
  "payload": {}
}

Event payloads are the protocol standard and are included in full. The events page may filter event inclusion with max_level=info|verbose|debug; filtering does not alter payload. The live event stream is raw and does not support level filtering.

Breaking migration from the removed projection contract:

Public ingress

MethodPathInputsSuccess responseStabilityNotes
POST/enqueueEnqueueRequest; auth header when bearer mode is active.{ ok, agent_id, message_id }Candidate stableEnqueues to the default agent.
POST/agents/:agent_id/enqueuePath agent_id; EnqueueRequest; auth header when bearer mode is active.{ ok, agent_id, message_id }Candidate stablePublic callers may not set trust or interject priority.
POST/webhooks/generic/:agent_idPath agent_id; JSON payload; auth header when bearer mode is active.{ ok, agent_id, message_id }Internal/debugCompatibility/debug route that converts the payload into a trusted integration webhook event. Prefer public enqueue or callback capabilities for new integrations.

EnqueueRequest fields:

FieldType / valuesRequiredNotes
kindchannel_event, webhook_event, etc.NoDefaults to webhook_event. Runtime-owned kinds such as system_tick and callback_event are rejected.
prioritynext, normal, background; interject only for trusted ingressNoDefaults to normal; public enqueue rejects interject.
trusttrusted_operator, trusted_system, trusted_integration, untrusted_externalNoPublic enqueue rejects caller-provided trust.
bodyMessageBodyNoUsed as-is when present.
textstringNoConverted to text body when body is absent.
jsonJSON valueNoConverted to JSON body when body and text are absent.
metadataJSON valueNoStored on the message.
correlation_id / causation_idstringNoPassed through to the message envelope.
originchannel or webhook for public enqueueNoPublic enqueue rejects operator, timer, system, and task origins.

Control actions

MethodPathRequest bodySuccess responseStabilityNotes
POST/control/agents/:agent_id/prompt{ text }{ ok, agent_id, message_id }Candidate stableEnqueues a trusted operator prompt with interject priority.
POST/control/agents/:agent_id/wake{ reason, source?, correlation_id?, causation_id? }{ ok, agent_id, disposition }Candidate stableEmpty reason is rejected. Does not start a stopped agent.
POST/control/agents/:agent_id/control{ action, authority_class? }; action is start or stop.{ ok }Candidate stableauthority_class is currently audit/provenance metadata only.
POST/control/agents/:agent_id/current-run/abort{ run_id?, mode?, authority_class? }{ ok, aborted, agent_id, run_id, mode, admission_context, provided_trust }Candidate stablemode defaults to stop_after_abort; deprecated alias pause_after_abort is accepted.
POST/control/agents/:agent_id/create{ template?, authority_class? }AgentSummaryExperimentalPath id names the created agent.
POST/control/agents/:agent_id/debug-prompt{ text, authority_class? }{ ok, agent_id, dump }Internal/debugDumps prompt rendering and should not be a stable automation API.

Tasks, work items, and timers

MethodPathRequest bodySuccess responseStabilityNotes
POST/control/agents/:agent_id/tasksCreateCommandTaskRequestTaskRecordCandidate stable for creation; DTO schema still broadserde(deny_unknown_fields) rejects legacy fields.
POST/control/agents/:agent_id/tasks/:task_id/input{ text, authority_class? }TaskInputResultCandidate stable route; DTO schema still broadDelivers operator-authority text to an interactive command task or supervised child-agent task.
POST/control/agents/:agent_id/tasks/:task_id/stop{ authority_class? }TaskStopResultCandidate stable route; DTO schema still broadRequests managed-task cancellation.
GET/agents/:agent_id/work-itemsnoneWorkItemRecord[]Experimental read model; CLI schema ownerQuery parameter: limit; used by holon work-item list.
GET/agents/:agent_id/work-items/:work_item_idnoneWorkItemRecordExperimental read model; CLI schema ownerUsed by holon work-item get; returns 404 when the id is not found for the target agent.
POST/control/agents/:agent_id/work-items{ objective, authority_class? }WorkItemRecordExperimentalCreates/enqueues work items.
POST/control/agents/:agent_id/work-items/:work_item_id/pick{ reason?, authority_class? }PickWorkItemResponseExperimentalSets the current WorkItem focus to an existing open WorkItem and returns the previous/current focus transition.
PATCH/control/agents/:agent_id/work-items/:work_item_idUpdateWorkItemRequestWorkItemRecordExperimentalMutates objective, plan status, todo list, and blocker/recheck fields. Empty mutations are rejected.
POST/control/agents/:agent_id/work-items/:work_item_id/complete{ authority_class? }WorkItemRecordExperimentalMarks an open WorkItem completed; cancel/delete remains out of scope.
GET/agents/:agent_id/timers/:timer_idPath agent_id, timer_id.TimerRecordCandidate stable route; DTO schema still broadReturns 404 when the timer id is not found for the target agent.
POST/control/agents/:agent_id/timers{ duration_ms, interval_ms?, summary?, authority_class? }TimerRecordCandidate stableduration_ms is required; interval_ms makes a repeating timer.
POST/control/agents/:agent_id/timers/:timer_id/cancel{ authority_class? }TimerRecordCandidate stableIdempotent for already-cancelled timers. Missing timers return 404; completed timers return 400 because they already fired.

CreateCommandTaskRequest fields:

FieldTypeRequiredDefault / notes
summarystringYesTask summary.
cmdstringYesCommand line passed to the command task runner.
workdirstring or nullNoOptional working directory.
shellstring or nullNoOptional shell.
loginboolNoDefaults to true.
ttyboolNoDefaults to false.
yield_time_msintegerNoDefaults to 10000.
max_output_tokensinteger or nullNoBounded output preview budget.
accepts_inputboolNoDefaults to false.
authority_classAuthorityClass or nullNoDefaults to operator authority for admitted control-plane requests; recorded in provenance/audit.

Workspace and model controls

MethodPathRequest bodySuccess responseStabilityNotes
POST/control/agents/:agent_id/workspace/attach{ path, authority_class? }{ ok, agent_id, workspace_id, workspace_anchor }Candidate stablepath is converted into a workspace entry.
POST/control/agents/:agent_id/workspace/exit{ authority_class? }{ ok, agent_id }Candidate stableReturns agent to default workspace behavior.
POST/control/agents/:agent_id/workspace/detach{ workspace_id, authority_class? }{ ok, agent_id, workspace_id }Candidate stableworkspace_id is trimmed before use.
POST/control/agents/:agent_id/model{ model, reasoning_effort?, authority_class? }{ ok, agent_id, model }Experimentalreasoning_effort must be low, medium, high, or xhigh.
POST/control/agents/:agent_id/model/clear{ authority_class? }{ ok, agent_id, model }ExperimentalClears the agent-level model override.

Operator transport integration

MethodPathRequest bodySuccess responseStabilityNotes
POST/control/agents/:agent_id/operator-bindingsOperatorTransportBindingRequest{ ok, agent_id, binding }Experimentaltarget_agent_id, when provided, must match the route agent_id.
POST/control/agents/:agent_id/operator-ingressOperatorIngressRequest{ ok, agent_id, message_id }ExperimentalRequires an active binding and matching actor/provider. Enqueues a trusted operator prompt.

OperatorTransportBindingRequest is strict (deny_unknown_fields) and contains binding_id?, transport, operator_actor_id, target_agent_id?, default_route_id, delivery_callback_url, delivery_auth, capabilities, provider?, provider_identity_ref?, and metadata?.

delivery_auth.kind = "bearer" requires a non-empty bearer_token. delivery_auth.kind = "hmac" is rejected until HMAC signing is implemented.

Skills

MethodPathRequest bodySuccess responseStabilityNotes
POST/control/agents/:agent_id/skills/install{ kind } where kind is a SkillInstallKind tagged union.{ ok, agent_id, skill_name }ExperimentalInstall errors can map to conflict, failed dependency, bad gateway, or gateway timeout.
POST/control/agents/:agent_id/skills/uninstall{ name }{ ok, agent_id, skill_name }ExperimentalRemoves a skill by name from the agent home.

Callback capability ingress

MethodPathInputsSuccess responseStabilityNotes
POST/callbacks/enqueue/:callback_tokenCapability token in path; arbitrary body; Content-Type guides body decoding.{ ok, ...CallbackDeliveryResult }CapabilityToken must resolve to an active external trigger with enqueue delivery mode.
POST/callbacks/wake/:callback_tokenCapability token in path; arbitrary body; Content-Type guides body decoding.{ ok, ...CallbackDeliveryResult }CapabilityToken must resolve to an active external trigger with wake delivery mode.

Body decoding rules:

Shared record shapes to stabilize

The following response types are exposed by multiple endpoints and should be treated as schema surfaces, not incidental Rust structs:

ShapeReturned byKey stability concerns
AgentSummary/agents/:id/status, /agents/:id/state, agent creationIdentity visibility/ownership/profile fields, status enum, model state, workspace fields.
AgentListEntry/agents/listKeep lightweight; avoid reintroducing heavy runtime/model payloads.
TaskRecord/agents/:id/tasks, task creation, state snapshot, eventsTask kind/status enums, detail truncation, recovery metadata, output references.
WorkItemRecordwork-item creation, state snapshot, eventsState, plan status, plan artifact, todo list, blockers/recheck timestamps.
TimerRecord/agents/:id/timers, /agents/:id/timers/:timer_id, timer creation, state snapshotRepeating timer fields and status enum.
BriefRecord/agents/:id/briefsUser-facing delivery vs internal traces.
TranscriptEntry/agents/:id/transcriptPotential provider/tool internals and truncation policy.
StreamEventEnvelopeevents page and SSE streamProjection/redaction, provenance, payload versioning.
RuntimeStatusResponseruntime readiness/statusStartup/runtime config surface and credential redaction.
SkillInstallKindskills installTagged union variants and local/remote package semantics.

Detected contract gaps

  1. OpenAPI route/type metadata is still only partially colocated with implementation. Route coverage and schema snapshots now exist, but the generated baseline still depends on a conservative OpenAPI table. Phase 2 moves operation metadata closer to Axum route/type definitions.
  2. Stable DTO schemas need tightening. Several task, work-item, timer, agent, event, and envelope responses are still represented broadly in the OpenAPI baseline and should become first-class typed components where they are stable client contracts.
  3. Event level filtering needs more real-world coverage. max_level inclusion rules should be validated against real TUI/client sessions before they are treated as final.
  4. WorkItem mutation APIs now cover focus/update/complete. HTTP can list/get/create/enqueue, pick focus, update objective/planning/blocker fields, and complete work items. Cancel/delete remains intentionally out of scope until a distinct lifecycle contract is needed.
  5. Timer lifecycle APIs now cover cancellation. HTTP can create/list/get timers and cancel active timers. Delete/purge remains out of scope.
  6. Deployment guidance still needs hardening. The HTTP ingress trust/auth table is documented, but production-facing guidance should still describe when to use bearer mode, local mode, callback capabilities, and dedicated operator adapters.
  7. Tool schema is a separate API surface. Built-in tool input/result schemas now have their own checked inventory; this HTTP inventory should link to it rather than duplicate it.

Tracking issues

Milestone 8 baseline issues are complete. Phase 2 follow-up issues are grouped under the same CLI/API Stability Contracts milestone and tracked by #1444.

IssueScope
#1438 api: migrate OpenAPI baseline to aide route/type metadataMove OpenAPI operation metadata closer to route and DTO definitions.
#1439 api: tighten OpenAPI DTO schemas for stable read modelsReplace selected generic JSON schemas with typed stable DTO schemas.
#1443 events: define stable operator-facing event payload subsetVersion/document stable event fields. Event payloads are the protocol standard; max_level filters event inclusion only.

Suggested next work

  1. Migrate the OpenAPI baseline toward aide route/type metadata (#1438).
  2. Tighten DTO schemas for stable read models and control-plane results (#1439).
  3. Define the stable operator-facing event payload subset (#1443).