HTTP API

AgentInbox exposes a local control-plane HTTP API over either:

The HTTP surface is now resource-oriented and is intended to remain stable going forward.

Meta

Hosts

POST /hosts accepts the shared provider/runtime host configuration:

{
  "hostType": "github",
  "hostKey": "uxcAuth:github-default",
  "config": {
    "uxcAuth": "github-default"
  }
}

Streams / Sources

GET /streams/{streamId} and GET /sources/{sourceId} preserve their existing detail fields and add streamStats (null when no stream exists). eventCount remains the total number of identity rows, including pruned events; replayableEventCount and prunedEventCount distinguish payload availability. prunedThroughOffset, retainedFromOffset and highWatermarkOffset expose replay boundaries. The new compaction-specific fields may be absent for third-party backends.

POST /streams/{streamId}/compaction-preview accepts {"retentionMs": 2592000000, "scanLimit": 1000} and performs a bounded, read-only payload-compaction preview. The path uses the public source/stream ID. It does not initialize a missing event stream or invoke a provider. See CLI preview semantics for consumer protection, sample counts, byte estimates and truncation fields. Invalid durations/limits return 400, missing streams return 404, and a backend without preview support returns 501. This endpoint cannot apply compaction.

POST /streams/{streamId}/compaction-apply is a separate, manual opt-in endpoint:

{"apply": true, "retentionMs": 2592000000, "batchSize": 100, "maxEvents": 1000, "maxPayloadBytes": 8388608}

apply: true and a positive retentionMs are required. The optional limits above are both defaults and hard upper bounds. Unknown options (including preview-only scanLimit) and missing/false apply return 400; missing streams return 404 and unsupported backends return 501. No-consumer streams are skipped. Eligibility is rechecked per short batch; retries skip committed pruned payloads. A single event over the byte budget blocks progress with stopReason: oversized.

Responses report committed batches, pruned logical bytes, replay bounds, nextOffset, hasMore and the stop reason. Identity stubs and historical offsets remain, but payload pruning cannot be undone without a backup. There is no automatic GC, VACUUM, or promise of immediate database-file shrinkage. See manual compaction and replay boundaries for latest, earliest, at_offset, at_time, reset and commit behavior. History-unavailable failures return 409 with error, code: history_unavailable, streamId, requested (offset/time/startPolicy), retainedFromOffset, and highWatermarkOffset; these fields also survive CLI remote errors.

The canonical registration shape is host+stream:

{
  "hostId": "hst_...",
  "streamKind": "repo_events",
  "streamKey": "holon-run/agentinbox",
  "config": {
    "owner": "holon-run",
    "repo": "agentinbox"
  }
}

POST /sources is the stream-oriented creation endpoint. GET /sources/{sourceId} and related routes continue to operate on stream/source instances after registration.

Agents

pause persists agent.deliveryPaused independently of aggregation policy and target offline state. It gates all proactive webhook/terminal notifications, including digest, retry and timer notifications. Collection, matching, inbox materialization, reads and explicit outbound replies continue. A request already started before the pause may finish.

Resume with {"deliveryOnly":true} clears this flag and catches up once per effective active target using persisted unacknowledged entries, without creating inbox entries. Existing target offline state and retry/defer backoff are preserved. Repeated resume calls retry due durable pending work, but never replay successfully notified backlog. Pending catch-up is persisted before clearing the pause, so request failure or process interruption can be retried by resume or the daemon. Pause survives daemon restarts.

deliveryOnly defaults to false. With an omitted body or {}, resume retains legacy recovery of all offline targets and also clears the delivery pause, regardless of pause history. Concurrent calls with different scopes are serialized so target recovery is not lost. Target-level resume only recovers that target and never clears the agent delivery pause.

Activation Targets

Timers

POST /timers accepts a narrow reminder body:

{
  "agentId": "agt_...",
  "at": "2026-04-15T08:00:00+08:00",
  "message": "Analyze the agentinbox project and prepare today's task plan.",
  "sender": "timer"
}

Use exactly one of at, every, or cron. every is an interval in milliseconds. timezone is optional and defaults to the daemon host timezone.

Subscriptions

POST /subscriptions accepts:

{
  "agentId": "agt_...",
  "sourceId": "src_...",
  "filter": {},
  "trackedResourceRef": "pr:373",
  "cleanupPolicy": {
    "mode": "on_terminal_or_at",
    "at": "2026-05-01T00:00:00.000Z",
    "gracePeriodSecs": 300
  }
}

cleanupPolicy defaults to {"mode":"manual"} when omitted.

POST /subscriptions always returns an envelope:

{
  "subscriptions": [
    {
      "subscriptionId": "sub_...",
      "agentId": "agt_...",
      "sourceId": "src_..."
    }
  ]
}

Non-shortcut creation returns one element. A shortcut may expand into multiple subscriptions, so callers should always read subscriptions[].

Inbox

POST /agents/{agentId}/inbox/items accepts a narrow direct-text ingress body:

{
  "message": "Review PR #51 CI failure and push a fix.",
  "sender": "local-script"
}

message is required. sender is optional.

POST /agents/{agentId}/inbox/ack accepts exactly one of:

{ "throughEntryId": "ent_..." }
{ "entryIds": ["ent_..."] }
{ "all": true }

Notes