Decisions
A Workstream remembers a great deal of context: what was discussed, what was tried, what somebody suggested. A decision is the narrower thing your organization actually committed to — a course, the reason it was chosen, who authorized it, and what it changed in the outside world. Approving a card records one. So does an agent committing to a strategy that changes nothing outside Erdo, such as which audience a campaign targets. The record exists to answer questions that used to have no data behind them. What did we decide about this campaign, and why? Did the change actually go through? Did leads or cost move afterwards, and how strong is that evidence? And the one every other question is really asking: can this be trusted to make these calls without me? A decision is not a log line. It is authoritative until an explicit later decision supersedes it, which is what lets an agent picking work up weeks later continue what you agreed rather than re-argue it — and what makes a change of course visible, because reversing a decision records a new one naming the decision it replaces.Decisions are part of the work engine and roll out with it. Until Workstreams are enabled for
your workspace, these endpoints and tools return
permission_denied (“workstreams are not
enabled for this organization”) — contact us to get switched on.What a decision carries
- The commitment —
whatin one line of business English, andwhy: the argument the proposal was made on. When the person deciding gave their own reason for accepting, refusing or correcting it, that is kept separately asdecider_rationale. The two are never merged: a proposal’s argument read months later as the approver’s reason for saying yes is a motive nobody stated. - The class — a stable, provider-agnostic name for the kind of decision this is
(
paid_media.ad_group.pause,page.publish,experiment.variant.stop). It is what the scorecard groups on, so it survives a tool being renamed or split. - Applicability —
standingfor a course that outlives the work it authorized,one_shotfor an authorization covering exactly the actions it named. A page that published on Tuesday is not a course anybody continues; “target finance leaders on this campaign” is. - The authority — who exercised the judgement (
human,llm_agent,deterministic_policy,safe_default,system) and, where relevant, which person and which agent run. - The subject — the campaign, page, or lead form the decision is about, so the record is searchable by the thing that changed.
- The ask it answers — for a decision that came from an approval, the
approval_idof the card somebody answered. It is on the row and it is also a filter, so you can go from an approval straight to what answering it produced, whether the answer was yes or no. One approval can name several decisions: a batch is split into one decision per coherent intent once it is answered. - Actions — one row per exact external call the decision authorized, each with its own subject and its own execution result. A batch that pauses four ad groups keeps four results, so a partial success stays visible instead of collapsing into one flag.
- Expected effects — what the decision said, in advance, it would move: the metric, the predicate that counts as success, the windows to judge it over, and any guardrails. Most decisions declare none, and that is the honest answer rather than a gap — a permission fix has no business effect anything could measure.
The lifecycle
effective means the change is live in the outside world. measuring means its declared
expectation is being watched. settled means the evidence came in. censored means something
intervened — usually a later decision replacing this one mid-window — so there is no honest
before-and-after left to compute; the decision still stands, what was lost is the ability to
score it.
An action Erdo could not confirm ends unknown rather than succeeded or failed. That state is
kept deliberately: the change may be live and nothing verified it, and it is resolved by reading
the provider back, never by trying again.
How an outcome is described
Every settled outcome carries the kind of evidence that produced it, and the words Erdo uses depend on it. This is not a stylistic preference — a before-and-after movement presented as proof the change caused it is the most damaging thing this record could produce, so the vocabulary is fixed to what the evidence can support:
An uncertain or stale result is
inconclusive under every kind, with a reason recorded — a
result nobody can explain is a result nobody can act on.
Erdo sends the phrase itself as outcome_label on every read, so each surface says the same
thing about the same row rather than deriving its own wording.
The scorecard
decisions scorecard reports raw counts with their denominators. It deliberately publishes
no eligibility verdict and no single pooled “worked rate”:
- Totals — every decision in the window, split by status and by producer family, with the backfilled legacy history named separately so the numbers reconcile.
- Execution — how many decisions authorized an external call at all, how many actions ran, and how each ended, with the unconfirmed ones counted rather than absorbed into a success rate.
- Measurement coverage — how many decisions declared an expectation, how many effects exist, and where each one sits: settled, censored, or waiting on a measurement Erdo cannot yet read. A met rate over six settled effects out of two hundred decisions is a different claim from the same rate over a hundred and eighty, so the denominator travels with it.
- Outcomes, stratified — the settled split cut by decision class and evidence kind, and again by producer family and evidence kind. Never pooled: averaging a deterministic confirmation against an observational movement produces a number that is wrong in whichever vocabulary you read it in.
- Deciders — who exercised the judgement, how often the safe default applied because nobody answered, and how often a person refused something the system proposed.
- Time to authorize — the median and 90th percentile wait between a decision being proposed and being authorized.
CLI
erdo decisions list prints one row per decision: the slug, its status, its execution result,
what it was, and — only once something settled — how it turned out, in the words its evidence
supports.
erdo decisions show prints the drill-in in the order the record means something: the commitment
and its authority, then what was actually attempted, then what was expected and what the evidence
said, then what this decision replaced or was replaced by.
Decisions are referenced by slug, never UUID. Commands act on your active organization; pass
--org <idOrSlug> to pin a different one.
MCP tools
Identity and access ride the request context, so a caller only ever reaches their own
organization’s record — and inside it, only the projects they can view. A decision filed under a
workstream inherits that workstream’s project access, so a teammate who was never given a
restricted project sees none of its decisions in the search, cannot open one by slug, and does not
have its rows counted into the scorecard’s totals. Decisions that belong to no workstream — a page
publish, an email send — are organization-level and stay visible to everyone.
REST
Base URL
https://api.erdo.ai. Authenticate with Authorization: Bearer <token> and select the
org with X-Organization-ID.
GET /v1/decisions pages with limit (default 50, max 200) and offset, newest first. An
unrecognised value in a closed vocabulary — a misspelled status, a source that does not
exist — is refused rather than ignored, because silently dropping the filter would return the
whole organization to a caller who asked for one slice of it. approval_id is refused the same
way when it is not a UUID, and it narrows within your access rather than around it: an approval
answered inside a project you cannot view matches nothing.
GET /v1/decisions/:slug returns the decision, its actions, its effects, and its lineage. A slug
belonging to another organization comes back 404 rather than 403, so a slug can never be
probed for existence.
GET /v1/decisions-scorecard takes since and until as RFC3339 timestamps (since inclusive,
until exclusive) and echoes the window back on the response, so a readout can never be quoted
without the period it covers.
