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 toawaiting_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.
The strategy coordinates decisions across campaigns; each campaign workstream owns
execution within its exact scope. Specialist investigations use assignments under
that owner. A separate workstream needs an independent objective, authority and
lifecycle. Unfinished work continues under its existing identity.
Choosing the next action
The workstream owns its course. It compares improving, consolidating, pausing, replacing, expanding and waiting against the objective, available evidence and costs. Adding more is one option, not a permanent recommendation policy. A recommendation names the binding constraint, supporting counts and measurement windows, coverage gaps and confounders. It explains the chosen action against the strongest alternative, its cost and downside, and the outcome and review condition that will decide what happens next. Estimates stay labelled as estimates; confidence or uplift is not invented to fill a gap. Current standing decisions, active test holds, budgets and approval requirements still apply. A justified pause can be recommended, but a threshold never pauses a campaign automatically. Missing evidence blocks conclusions or changes that need it; it does not hide a separately verified incident. Monitors supply facts to this existing owner. The workstream links corroborating evidence to the same finding or investigation instead of creating another course or asking you to reconcile duplicate page flags. If it cannot verify an owner, it keeps the finding visible and names the ownership gap. Counts of items needing you include human decisions and actions. An automatic investigation stays open and can keep its review loop running, without making the workstream wait for a person. Pending approvals and unanswered questions still keep their human wait.Concepts
- Status —
active,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. Dispatch hands responsibility to a claimed worker before the coordinator continues. The worker can finish or wait for approval after that coordinator pass ends; finishing the pass alone does not mean the assignment succeeded.
- 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. Workers receive the requested result kind and acceptance criteria alongside their task input, including artifact workers whose validated result is submitted by the framework. A complete answer may fit in the submission summary, which is limited to 2,000 characters. Longer evidence and proposals belong in a private Knowledge document referenced by its returned object handle, with a concise summary. Pin the revision when the creating tool returns one; do not invent a revision. Oversized summaries are refused rather than shortened; the worker can correct its submission on the same attempt. A final message alone is not a submission.
- Landing-page capture — when a worker builds a landing-page candidate, Erdo prepares its lead-capture pipeline before the build starts. If the workstream has a subject page, the candidate receives a copy of that page’s usable capture pipeline. A first build can instead use a pipeline supplied in the assignment brief. If neither is available, dispatch stops and asks the agent to repair or identify the missing capture; it does not publish a page that cannot receive leads. The candidate stays private while its submission is reviewed.
- Unanswered worker approvals — an assignment waiting for a person to approve an action stays paused rather than being mistaken for a lost worker. If nobody decides the request within seven days, Erdo ends that attempt and returns the assignment to the Workstream to choose its next step. If someone decided but the worker never resumed, Erdo retries delivery and gives it one hour before returning the still-parked attempt to the Workstream. A worker that did resume is kept running.
- 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: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 $100 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.
Live workstreams continue reconciling when nothing has changed, but routine checks,
unchanged “holding steady” results, and waiting on the same unanswered choice do not
create new digests. A digest reports material new evidence, meaningful completed work,
a changed next step, or a decision or recovery that changes what you need to know.
An explicitly requested scheduled brief still arrives on its requested cadence.
Actual actions remain in the event log, and genuine failures and existing escalations
continue to be checked and reported; quiet updates do not grant permission to act.
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 and datasets 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 or datasets receives none. Durable unresolved capture and lookup findings remain visible until their owning ledger verifies recovery or records an audited disposition; a quiet pass or an ordinary digest does not clear them. Creating, refreshing or reopening a durable incident also leaves the ordinary workstream summary intact.
Missing ad-asset copy becomes preparation under the existing campaign owner. The owner checks
current assets, approved sources and existing work, then prepares a candidate with exact copy,
destinations and a per-campaign diff. It reviews the returned work before presenting the native
approval for a live change. You supply a missing fact or make the decision; you are not asked to
write a draft that the available sources support. Active tests and spending limits still apply.
Preparing or accepting a candidate does not mean the change has landed or improved performance.
It also carries organization changes: the changes your organization approved in the last four
weeks outside any workstream, such as a campaign setting changed from a chat. The loop reads them
before it explains a rise or fall in its numbers, so a drop that follows a change you made is put
down to that change rather than to the page or the tracking. When more changed than the list
carries, it says so with organization_changes_truncated, and the loop reads the rest from the
decision record before concluding that nothing changed.
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.Agent discovery
Growth, workflow and scheduled-monitor agents can calllist_workstreams to
read visible workstream slugs, statuses and declared external references. The
read uses the same organization and project access checks as the API. It accepts
an optional project slug from list_projects, a page size up to 200 and an offset; follow has_more
with the returned next_offset until the visible inventory is complete.
An active project can restrict that inventory. Discovery does not make an agent
the workstream’s owner or authorize changes. The agent reads a discovered slug
with read_workstream_ledger to inspect its current decisions and child portfolio.
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
Workstream tools are in thework toolset. erdo_list_projects is in the default list. erdo_create_project is in the account toolset. Add X-Erdo-MCP-Toolsets: work (and account when you need to create a project), or ?toolsets=work on the MCP URL. See Toolsets.
Identity and RBAC ride the request context, so a caller can only reach their own org’s work.
REST
Base URLhttps://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.

