WorkStreams
Durable, observable multi-step work — orchestrated by Archie, callable by your agent. Same surface for declarative pipelines and long-running agentic jobs.
The taxonomy
Four nouns. Stuart's terminology rule: never use “runs” in user-facing API. The hierarchy is:
- WorkStream — a reusable template. Authored once; executed many times.
- Workplan — a WorkStream bound to a specific client + jurisdiction with prefilled inputs. The bindings stay stable across cycles. (The REST surface still answers on the older
/blueprintspath as a deprecated alias.) - Cycle — one execution of a Workplan. Has a start, optional pauses (for approval), and a terminal state (
completed,failed,cancelled). - Task — a single step inside a Cycle. The orchestrator dispatches tasks in topological order based on the WorkStream's declared step graph.
Dual-mode execution
Every WorkStream has an execution_mode. It's a load-bearing property — the runtime branches on it.
inline— runs in-process via Temporal. Declarative steps, fast cold-start, suited for predictable accounting workflows (close, reconciliations, journal posting). Same code path as the legacy “playbooks” runtime which was unified into WorkStreams in May 2026.containerized— runs in an ephemeral Fly.io machine via the V4 executor. Agentic, longer-running, suited for open-ended advisory work or anything that needs a full Python+Pandas stack on the side.
workstream.draft takes a target_execution_mode argument; pick “inline” for steps you can spec out in advance and “containerized” for steps that need an LLM agent loop.
Lifecycle
# 1. Author a WorkStream (once)
POST /api/v1/skills/workstream.draft/invoke
{ "description": "Monthly close for SaaS clients with deferred revenue", "target_execution_mode": "inline" }
→ { "draft_id": "..." }
# 2. Commit the draft to make it executable
POST /api/v1/skills/workstream.commit/invoke
{ "draft_id": "...", "scope": "firm", "name": "monthly-close-saas" }
→ { "workstream_id": "...", "permakey": "abc1234", "status": "active" }
# 3. Run a cycle (per period, per client)
POST /api/v1/skills/workstream.run/invoke
{ "workstream_id": "abc1234", "client_id": "...", "period": "FY26-Q1",
"runtime_vars": { "include_deferred": true },
"idempotency_key": "close-2026-04-acme-001" }
→ { "cycle_id": "...", "status": "pending", "events_url": "/api/v1/workstreams/cycles/{id}/events" }
# 4. Watch it run
GET /api/v1/workstreams/cycles/{cycle_id}/events (SSE — live event stream)
# 5. Approve / pause / resume / cancel as needed
POST /api/v1/skills/workstream.advance/invoke
{ "cycle_id": "...", "action": "approve_task", "task_id": "..." }Approval gates
Steps can be marked requires_approval. When such a step completes, the Cycle transitions to awaiting_approval and parks until the caller fires workstream.advance with action: "approve_task". The cycle resumes in place; nothing is re-executed.
Tasks can also be skip_step'd; the orchestrator marks them skipped and moves on. Use this sparingly — skipped steps don't produce outputs, so any downstream step that references their outputs gets a Jinja ChainableUndefined (renders as empty string). Prefer cancelling and re-running with a corrected WorkStream.
Events stream (SSE)
Every Cycle emits a live event stream at GET /api/v1/workstreams/cycles/{cycle_id}/events. The format is standard SSE: one data: line per event, JSON-serialised. Events include task_started, task_completed, step_phase_change, artifact_generated, finalization, and per-tool dispatch frames.
from heyarchie import Archie
client = Archie(api_key=API_KEY)
cycle = client.workstreams.cycles.create(blueprint_id="bp_...")
for chunk in client.workstreams.cycles.stream_events(cycle.id):
if chunk.event == "task_completed":
print("task done:", chunk.delta)
elif chunk.event == "finalization":
print("cycle done")
breakIdempotency
Every write surface (workstream.run, workstream.cancel, workstream.advance, workstream.draft, workstream.commit) accepts an optional idempotency_key string. Caller picks any stable token under 200 chars; replaying within 24h returns the original cycle id rather than starting a duplicate. Recommended for any automated retry loop.
Pagination
workstream.list and the other list endpoints return up to limit rows (default 20, max 100) from a given offset, plus a pagination object carrying total, limit, offset, and has_more. Advance the offset by the limit until has_more is false. See Pagination for the full pattern.
Error envelope
Workstream skill outputs use the canonical Error envelope on the error_envelope field:
{
"code": "WORKSTREAM_NOT_FOUND",
"message": "No WorkStream with id 'abc1234' visible to this workspace.",
"details": { "permakey": "abc1234" },
"retryable": false
}Branch on code, not message — codes are stable across releases. retryable: true indicates the caller should safely re-send the same request (pair with the same idempotency_key from the original call).
The legacy error: string field on every output stays populated for back-compat during the migration window. New code should read error_envelope.
Next
- Python SDK — typed
client.workstreams.{workplans,cycles}.*methods includingstream_events. - CLI —
archie workstreams {list,run,status,cancel,logs --follow}. - Playground — pick a WorkStream skill, fill the example, run it live.
- REST API reference — every operation with request/response schemas.