Skip to main content

Workstreams

A Workstream is a record of multi-step business work in motion — a campaign launch, a lead engine, a weekly report, a communication loop. One per business outcome. Each has a status, an ordered set of phases, attached resources (datasets, pages, jobs, experiments), and a chronological event log — a git-log-style record of what happened and what’s next. In your workspace, Workstreams live under Activity. As an agent takes on a multi-step job it opens a Workstream and keeps it current — advancing phases, logging what it did, and flipping status to awaiting_user when it needs you. You watch progress, read the event log, and pick up or hand work back from there. A Workstream can host Experiments that decide whether a change worked. A Workstream opened inside a conversation is also linked back to the thread that started it: the thread’s Work panel shows its phases and assignments moving while the run is going, so you can watch a build progress without leaving the chat. The link is made automatically when the agent creates the Workstream. A root workstream can also operate as a Strategy: attach campaign workstreams as direct child resources and read their one-level portfolio from the root ledger.

Concepts

  • Statusactive, awaiting_user (paused for input/approval), blocked (external blocker), completed.
  • Phases — ordered steps (brand-brief, landing-pages, …), each with its own status.
  • Event log — append short factual lines ("Brand brief drafted, sent for review", "Blocked: Google Ads admin access missing", "Next: build the landing pages"). This is how you and future agents read history and what remains.
  • Assignments — bounded responsibilities that can run in parallel and survive the coordinator that created them. An assignment stays the same piece of work when a worker is retried; each try is an attempt.
  • Submission and adoption — a worker submits a candidate result, pinned to the exact Page, Knowledge, or Dataset revision it produced. Technical completion moves the assignment to review; it does not make the result authoritative. The Workstream accepts, rejects, or supersedes it explicitly.
  • Decisions — a workstream remembers a great deal of context: what was discussed, what was tried, what somebody suggested. A decision is the narrower thing the business actually committed to — a course, the reason it was chosen, and who authorized it — and it stays the standing course until a later decision explicitly replaces it. That distinction is what lets an agent picking the work up weeks later continue what you agreed rather than re-argue it, and it makes a change of course visible: reversing a decision records a new one naming the decision it supersedes. A decision does not need an external change behind it — approving an action records one, and so does an agent committing to a strategy that changes nothing outside Erdo, such as which audience a campaign targets. The full record — every decision, what executed, and what the evidence said afterwards — is searchable on its own surface.

Projects

Every Workstream lives in a project — the work context that groups related work and can grant project members access to attached resources. The organization remains the tenant and security boundary; selecting a project focuses project-owned lists rather than creating another tenant. Creating a workstream requires a project slug, so if you’re driving Erdo headlessly and don’t have one yet, create it first. Projects are referenced by slug, and project membership is managed in the app (there’s no programmatic member surface). Listing and creating projects is available on all three surfaces:
The MCP tools are erdo_list_projects and erdo_create_project; the REST mirrors are GET /v1/projects and POST /v1/projects (body: name, slug, optional description).

Arming the loop, the ledger, and resources

A workstream can be driven entirely by hand, or handed to Erdo’s reconciliation loop to run on its own cadence. Arming attaches that loop to one workstream with a budget envelope — a token spend cap and a daily escalation cap — so autonomous work can never spend or interrupt past a limit you set. Nothing runs autonomously until you arm it; how much the loop may change live, customer-facing state on its own is a separate control, the autonomy mode. Set the budget envelope on the workstream state (token_budget_millicents, escalation_budget_per_day) before or while arming, or pass token_budget_millicents when you create the workstream. The spend cap is in millicents: 100,000 per dollar, so 10000000 is 100and25000000is100 and `25000000` is 250. Size it for the whole outcome rather than one sitting — a lead engine that builds pages, runs a campaign and keeps a loop reading for weeks spends in the low hundreds of dollars, and a workstream that reaches its ceiling can fund nothing: every allocator recommendation becomes a hold, dispatch and fan-out refuse, and the loop pauses and files an attention item asking you to raise the ceiling or ship what it has. Raising it resumes the loop on its next scheduled pass and resolves that item. The ledger is the loop’s single source of truth, read in one call: the budget envelope, the workstream’s experiments and their recent observations, the judge calibration record and trust dial, the decisions currently in force, your steering notes, open attention items, and the allocator’s current recommendation. Current decisions arrive with the ledger rather than being something the loop has to go looking for, because a course you approved is only worth recording if the next pass reads it before it reasons — so the loop continues what is in force, and when it does want to change course it proposes a new decision naming the one it would replace. The ledger also carries monitor findings: what your organization’s monitors reported about the campaigns and ad groups this workstream declared as its scope, and about the pages attached to it, open, or raised (or raised again) in the last week. A monitor reports to the whole organization, so this is how the loop that manages a campaign reads a finding about it and acts on it in its next pass. A workstream with no declared scope and no attached pages receives none. Attaching a resource records that a page, dataset, experiment, job, thread, or another workstream belongs to this workstream in a named role (its output, an input, evidence, something it monitors), so the ledger and event log show the full shape of the work.

Programmatic access

You can also create, drive, and observe Workstreams over MCP, the REST API, or the CLI — the same surface an agent uses internally. Everything is org-scoped and RBAC’d (read needs project view, writes need contribute), and Workstreams are referenced by slug, never UUID.

CLI

create accepts --token-budget <millicents> so a workstream starts with its envelope. arm accepts --interval <min>, --model <id>, and --timezone <tz>; on both arm and set-state the --token-budget (millicents, 100,000 per dollar) and --escalation-budget (per day) flags set the workstream’s envelope — on arm that state update runs first, then the loop is armed. attach --type is one of page, dataset, heartbeat, experiment_run, thread, job, knowledge_object, approval, or workstream, and --rel is the relationship (output, input, evidence, monitoring, subject, parent, child, running), with optional --title and --phase. detach takes the same --type and --id; --rel is optional there and narrows the removal to one role, so omitting it removes every link the workstream holds to that resource. It prints removed_count rather than a success line — a detach that matched nothing answers 0, which is what you want to see when the id was wrong rather than the link stale. One link will not detach while it is doing work. A workstream’s monitoring heartbeat is its armed reconciliation loop, and that link is what charges the loop’s own spend to the workstream and tells the next arm that a loop already exists — so unlinking it while the automation still runs would leave the loop running against nobody’s budget and let a second one be armed beside it. Detaching a live one is refused, naming the automation and how to stand it down (erdo automations disable <id>). Wait for any pending or running executions to finish, then detach. Detaching permanently retires the stopped automation while preserving its execution history; it cannot be edited, enabled, run, or attached as a monitoring loop again, and its triggers cannot be deleted. Each reconciliation heartbeat belongs to one workstream, so another workstream cannot attach the same heartbeat as its monitoring loop. To resume reconciliation later, arm a new loop on the workstream. A link to an already-deleted automation can be removed immediately. The CLI authenticates with your Erdo token and the active organization (erdo login, erdo org).

MCP tools

Identity and RBAC ride the request context, so a caller can only reach their own org’s work.

REST

Base URL https://api.erdo.ai. Authenticate with Authorization: Bearer <token> and select the org with X-Organization-ID. POST /v1/workstreams takes project_slug, slug, title, and optional description, phases and token_budget_millicents (the spend ceiling, 100,000 millicents per dollar). POST /v1/workstreams/:slug/arm takes optional interval_minutes, model, and timezone (set or raise the budget envelope with PATCH /v1/workstreams/:slug/state). GET /v1/workstreams/:slug/ledger takes optional observations_per_experiment. POST /v1/workstreams/:slug/resources takes the resource type, id, relationship rel, and optional title and phase. DELETE /v1/workstreams/:slug/resources unlinks one, addressed the same way — resource_type and resource_id as query parameters, plus an optional relationship_type that narrows the removal to one role. It answers removed_count and the links it removed, so a call that matched nothing reports 0 instead of a bare success. It refuses with failed_precondition when the link named is a reconciliation loop that is still running — disable the automation first, wait for its executions to finish, then detach and retire it.