Operating system set to macOS
Documentation jurisdiction set to United States
Sign inConnect Claude

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 /blueprints path 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")
        break

Idempotency

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 including stream_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.