Problem
Nothing consults spend before spawning. relayburn ledgers cost after the
fact and burn mcp-server exposes it read-only and advisory, so a stuck recipe
burns until a human notices. Factory has a safety gate for what it may work
on and no gate for how much that may cost.
Fix
Two halves. The second only works if the first lands.
1. Attribution
Stamp every agent Factory spawns with the burn enrichment tags that already
exist:
workflowId = the dispatch lifecycle runId
tag issue=<workUnitId>
tag repo=<slug>
Without this, spend cannot be attributed to a run and any budget number is a
guess. With it, burn hotspots --workflow <runId> answers "what did this issue
cost" directly, and burn summary --group-by-tag repo answers it per
repository.
2. The gate
Add a budget block to factory.config.json, alongside safety and resolved
nearest-first the same way:
Evaluate it in the src/safety/ path where scope is already evaluated, so a
refusal lands in the existing skipped: Array<{ issue, reason }> channel on
IterationReport (src/types.ts:169) and surfaces through factory status and
the Slack thread instead of crashing a run.
The read is GET /v1/summary?since=<window> against burn-cloud with the
brn_at_* token.
Behavior:
- No
budget block → no gate, unchanged behavior.
- Over
maxUsd with onExceeded: "refuse" → every ready issue is skipped with
reason budget exceeded: $X of $Y in <window>.
- burn-cloud unreachable → refuse. A dispatch gate fails closed.
What this is not
/v1/summary is scoped by org, model, and machine, and burn-cloud has no
reservation concept. N concurrent dispatches can each pass the check and
collectively overshoot by up to N runs. This is a coarse circuit breaker, not an
atomic per-run debit.
Making it atomic means burn-cloud grows POST /v1/reserve plus
commit-on-completion. That is a burn-cloud change, not a Factory one, and is
worth filing only if the circuit breaker proves insufficient in practice.
Definition of done
- Agents spawned by a dispatch carry that dispatch's
workflowId and the
issue / repo tags; burn hotspots --workflow <runId> returns its spend.
- A workspace over
maxUsd reports ready issues as skipped with the budget
reason rather than dispatching them.
- burn-cloud unreachable refuses rather than dispatching.
- Absent
budget config produces no behavior change.
Problem
Nothing consults spend before spawning.
relayburnledgers cost after thefact and
burn mcp-serverexposes it read-only and advisory, so a stuck recipeburns until a human notices. Factory has a safety gate for what it may work
on and no gate for how much that may cost.
Fix
Two halves. The second only works if the first lands.
1. Attribution
Stamp every agent Factory spawns with the burn enrichment tags that already
exist:
workflowId= the dispatch lifecyclerunIdtag issue=<workUnitId>tag repo=<slug>Without this, spend cannot be attributed to a run and any budget number is a
guess. With it,
burn hotspots --workflow <runId>answers "what did this issuecost" directly, and
burn summary --group-by-tag repoanswers it perrepository.
2. The gate
Add a
budgetblock tofactory.config.json, alongsidesafetyand resolvednearest-first the same way:
Evaluate it in the
src/safety/path where scope is already evaluated, so arefusal lands in the existing
skipped: Array<{ issue, reason }>channel onIterationReport(src/types.ts:169) and surfaces throughfactory statusandthe Slack thread instead of crashing a run.
The read is
GET /v1/summary?since=<window>against burn-cloud with thebrn_at_*token.Behavior:
budgetblock → no gate, unchanged behavior.maxUsdwithonExceeded: "refuse"→ every ready issue is skipped withreason
budget exceeded: $X of $Y in <window>.What this is not
/v1/summaryis scoped by org, model, and machine, and burn-cloud has noreservation concept. N concurrent dispatches can each pass the check and
collectively overshoot by up to N runs. This is a coarse circuit breaker, not an
atomic per-run debit.
Making it atomic means burn-cloud grows
POST /v1/reservepluscommit-on-completion. That is a burn-cloud change, not a Factory one, and is
worth filing only if the circuit breaker proves insufficient in practice.
Definition of done
workflowIdand theissue/repotags;burn hotspots --workflow <runId>returns its spend.maxUsdreports ready issues as skipped with the budgetreason rather than dispatching them.
budgetconfig produces no behavior change.