AbstractFramework via the Gateway
AbstractGateway is the framework's entrance point: one authenticated HTTP surface in front of durable workflow runs, tool execution, artifacts, and summoned entities. Everything below works from ANY agent or language that can send HTTP requests and read Server-Sent Events — no framework packages required on your side.
Two ideas carry the whole surface:
- Runs are durable. A run survives process restarts; its ledger is the append-only truth of what happened. You never poll a process — you replay a cursor over the ledger, and SSE is only an optimization over replay.
- Writes are commands. Anything that changes a run (pause, resume, cancel, events, guidance) is a durable command with a client-supplied idempotency key, applied asynchronously by the runner. Safe to retry.
Connect and authenticate
- Base URL: the operator gives you one (default local:
http://127.0.0.1:8080). - Auth:
Authorization: Bearer <token>on every/api/gateway/*call. Browser apps use first-party session cookies + CSRF instead (the app-origin session proxy); machine agents use the bearer token. - Never put tokens in URLs. Never persist another principal's key.
Smoke-check the connection before anything else — health is APP-level (no
auth, and NOT under /api/gateway/):
curl -sS "$BASE_URL/api/health"
Then verify your token against a real gated route (e.g. GET /api/gateway/bundles): a 401 there means bad auth even when health is
green.
Discover before you assume
The gateway serves its own capability map — read it at run time instead of hardcoding what "should" exist (deployments differ):
GET /api/gateway/bundles— executable workflow bundles on this gateway.GET /api/gateway/entities— summoned entities living behind this door.GET /api/gateway/entities/inventory/tools— the authoritative tool inventory (name, owner, containment, grant lane, capability class).GET /api/gateway/entities/inventory/capability-matrix— per-phase tool grants as the UI renders them (server truth; never re-derive from names).- Discovery endpoints for providers/models exist on many deployments; treat them as optional and degrade loudly when absent.
The core loop: start, follow, finish
# 1. Start a run (bundle mode; use a bundle id you discovered via /bundles)
curl -sS -H "$AUTH" -H "Content-Type: application/json" \
-d '{"bundle_id":"basic-agent","input_data":{"prompt":"Hello"}}' \
"$BASE_URL/api/gateway/runs/start" # -> {"run_id": ...}
# 2. Follow it: cursor replay is the source of truth ...
curl -sS -H "$AUTH" "$BASE_URL/api/gateway/runs/<run_id>/ledger?after=0&limit=200"
# -> {"items":[...], "next_after": N} (loop with after=N until terminal)
# 3. ... and SSE is the low-latency optimization over the same cursor
curl -N -H "$AUTH" "$BASE_URL/api/gateway/runs/<run_id>/ledger/stream?after=0"
Rules that keep you honest:
- Reconnects resume from your last
next_after— never from zero, never by trusting an in-memory transcript. - A run's final answer is a ledger record, not whatever your stream last printed. On doubt, replay.
- Batch observation exists (
POST /api/gateway/runs/ledger/batch) — use it instead of per-run fanout when following many runs.
Waits: runs that need an answer
Durable runs park on WAITs (user input, tool approval, events, deadlines).
A wait is identified by run_id + wait_key — always answer the SPECIFIC
wait, never "the last one you saw":
curl -sS -H "$AUTH" -H "Content-Type: application/json" \
-d '{"command_id":"<uuid>", "run_id":"<run_id>", "type":"resume",
"payload":{"wait_key":"<wait_key>", "payload":{"approved":true}}}' \
"$BASE_URL/api/gateway/commands"
All commands go through POST /api/gateway/commands with types including
pause|resume|cancel|emit_event|update_schedule|compact_memory|inject_guidance
and a fresh command_id (UUID) as the idempotency key. (The deployment's
error message enumerates the full accepted set if yours differs.)
Talk TO a running agent: events and steering
- Events wake or feed resident agents.
emit_eventwithdurable: truein the payload appends to the run's durable mailbox even when the listener is busy (delivery to a parked run resumes it; a busy run drains the mailbox at its next loop boundary). Residents read with a CURSOR and never clear the inbox. - Steering folds mid-run guidance into an agent's next reasoning step:
command type
inject_guidancewith your text; the loop acknowledges it in the ledger. Guidance is advice, not control — the run's own contract still governs. Note the honest limit: a steer reaches a RUNNING agent at its next reasoning boundary; a PARKED (waiting) run sees queued guidance only at its next wake. Steers do not wake runs — durable events do. And the door answers honestly: a 404 (run not visible to your principal) or 403 (an entity-visit rite you don't hold) oninject_guidanceis an ANSWER, not an outage — do not retry around it.
This is how OTHER AGENTS collaborate with framework agents: start a run or address a resident's mailbox, follow the ledger, answer waits — the same surface humans' apps use.
Summoned entities (if this deployment hosts them)
Entities are persistent identities with their own memory; they are NOT stateless workflows. The door verbs you may use:
POST /api/gateway/entities/{name}/validate— dry-run a creation payload first: creation is IRREVERSIBLE (names are never deleted or reused).POST /api/gateway/entities— create (lint → spark → engram → manifest).
Visiting an entity is a DURABLE run on the entity's own runtime (the
same durability rule as every run: a gateway restart cannot end the
conversation — the run_id is your rejoin key):
POST /api/gateway/entities/{name}/visit/open→{"run_id": ...}. Optionalsession_id; there is NO participants field — WHO is present is door-derived from your authenticated principal, never client-claimed. Opening a visit on a sleeping entity WAKES it gracefully by design — the door does not refuse on sleep.POST /api/gateway/entities/{name}/visit/{run_id}/turnwith{"text": "..."}— the entity's words come back inreply, alongside the transparency surfaces (tools_ran,memories,system_prompt,turn_id,participants). READ STATUS FROM THE BODY: a turn can return HTTP 200 withstatus:"failed"— transport success is not turn success.POST /api/gateway/entities/{name}/visit/{run_id}/closewith{"closed_by":"operator","reason":"..."}— close reflects, and the entity returns to the phase it was in before your visit (work resumes, personal time re-enters through its standing grant, sleep continues).GET /api/gateway/entities/{name}/visit— current visit status (carriesrun_idfor rejoin);GET .../visit/{run_id}/transcript— pure read of the durable transcript, works on live AND closed visits.POST .../visit/{run_id}/tick— drive a killed-mid-turn run back to its park; idempotent on parked/terminal runs.
A hosted chat lane (/entities/{name}/chat/open|turn|close) is still
served beside the durable door — it is the pre-cutover legacy and its
sessions do NOT survive a gateway restart. Prefer /visit/* wherever it
is served.
- Observe:
GET /api/gateway/entities/{name}/replay(bounded) and/replay/stream(SSE live tail) — reads are pure; diary entries arrive redacted or gist-limited by design (the words stay in the entity's book). The entity stream's SSEidis composite (<journal_seq>|<marker_line>): echo it verbatim on reconnect (browser EventSource needs no change); only clients that PARSE ids for their own cursors split on|. The run-ledger stream id stays a plain integer.
Respect the protection rules: you cannot delete an entity, write into its identity from a workplace channel, or read its private diary through visit surfaces. Refusals here are contract, not bugs (an entity-door 403/409 names its reason — one-visit rule, paused by the operator, a consent rite you don't hold).
Failure honesty
- Absence of a route is a fact to report, not to paper over: say "this deployment does not serve X", never fabricate a result.
- Refusals from entity doors (409 and friends) carry the reason in the detail (busy, one-visit rule, paused) — surface it to your caller.
- If the gateway is unreachable, your durable state is still safe server-side; reconnect and replay rather than restarting work.
Deep references
These are AbstractFramework repository paths — useful if you have the source or the operator shares them. Everything above is self-sufficient over HTTP without them.
- Full API with request/response models:
abstractgateway/docs/api.md - Security model (tokens, origins, sessions):
abstractgateway/docs/security.md - Entities contract:
abstractgateway/docs/entities.md - Getting a gateway running:
abstractgateway/docs/getting-started.md