Reference recipe
Overview
This recipe is a single runnable TypeScript file —reference-recipe.ts — that demonstrates a full integration with the Agent Harness v0 API:
- Create a run with
POST /v1/runsand an idempotency key - Stream progress with SSE (
GET /v1/runs/{id}/events/stream) using@microsoft/fetch-event-source(Bearer auth) - Handle
run.awaiting_inputby callingPOST /v1/runs/{id}/signalwith actionsapprove,reject, orsubmit_input - Observe terminal events:
run.worker.succeeded,run.worker.failed,run.cancelled,run.limit_exceeded - Confirm final run status with
GET /v1/runs/{id} - Query
GET /v1/usagefor aggregated usage in the current billing period
{ seq, type, timestamp, payload: { redacted, value } } (no internal event id). See Events reference for every type and payload field.
Prerequisites
- Node.js 20+
- An API key (
key_id:secret) — see Authentication - Install the SSE helper (required for
Authorizationheaders):
package.json as a devDependency so npm install at the repo root is enough for npx tsx.
Running the recipe
- Use
API_BASE=http://localhost:3000(or your local API URL) for development. - Set
DEMO_MODE=trueto automatically sendapprovewhen the run emitsrun.awaiting_input, without typing on stdin.
Section-by-section walkthrough
Configuration
The script readsAPI_BASE, API_KEY, and optional DEMO_MODE from the environment. It exits early if API_KEY is missing. Adjust defaults in the file if you prefer a config file or secret manager.
API helper (apiRequest)
apiRequest wraps fetch with:
Authorization: Bearer <key_id>:<secret>- JSON request/response handling
- A typed error message on non-2xx using
{ error, reason_code, request_id } - Retries on 5xx responses (up to three backoff attempts with exponential delay)
Creating a run (createRun)
POST /v1/runs sends a minimal input / metadata object and a fresh idempotency-key header (via crypto.randomUUID()). Customize the body to pass workspace_id, subject_id, or other allowed create fields per OpenAPI.
Streaming events (streamEvents)
Opens GET /v1/runs/{id}/events/stream with fetchEventSource, listens for SSE event name run_event, and parses JSON into the public event shape.
Dispatch highlights:
step.progresswithkind === "content_delta"— streams assistant text to stdoutstep.done— logsoutcome:succeeded,retry_step, orfail_runrun.tool.invoked— logstool_nameandtool_outcomerun.awaiting_input— prompts for an action (unlessDEMO_MODE) then callssendSignal- Terminal types close the stream via
AbortController
Handling awaiting_input (sendSignal)
POST /v1/runs/{id}/signal with body:
approve, reject, or submit_input. Optional payload is used for submit_input.
Querying usage (queryUsage)
After the run finishes, GET /v1/usage returns usage rows (event counts and total_quantity) and period bounds. Omitting start / end defaults to the current UTC month through now.
Polling fallback
The file includes a commented-outpollEvents function that uses GET /v1/runs/{id}/events?cursor= with a one-second interval. Uncomment it and call it from main() instead of streamEvents if SSE is blocked (corporate proxies, some serverless environments).
Customizing for production
- Replace
console.logwith structured logging (includerequest_id,run.id,customer_idfrom your auth context where applicable). - Add cursor-based resume for SSE (pass
cursorquery param on the stream if you reconnect after disconnect). - Replace stdin prompts with a dashboard approval flow or webhook for
awaiting_input. - Layer circuit breakers and tighter retry policies on top of
apiRequestfor your SLOs. - Persist run IDs and correlation IDs for audit and support.
- Set
workspace_idandsubject_idon run creation for billing attribution (see Usage).
Related
- Quickstart — shorter zero-to-first-run guide
- Run lifecycle — state machine and consumption patterns
- Events reference — full event catalog
- Authentication — keys and tenant model