Xeo Forge v1.21.0 — Local-First Agent Workbench
v1.21.0 — The Chosen Boundary
Summary
v1.20.0 shipped autonomy levels as rule data with 22 contract tests — and
disclosed, on discovery, that they were not actually reachable: the API
never accepted one and startAgentRun never forwarded one, so every run
executed at the default. This release closes that gap completely, plus a
third one found while wiring it: the loop built its tool context before
computing the rule set, so even an explicit level could not have reached
the tools. Authority is now chosen, stored, enforced, and displayed —
end-to-end, with each layer's test pinning it.
Chosen before the run exists
Work setup gained field 04 — Authority — backed by the real level set
(read_only / assist / execute / autonomous), and WorkIntake's
authority card carries the same picker with the chosen level's contract
line rendered live. The level is stored on the task row (idempotent ALTER
for existing databases) and validated at every boundary: unknown values
fail loudly naming the valid levels, because a typo that silently coerced
to a broader level is an escalation hidden in plain sight. Follow-up
messages, plan approvals, mode switches, and rejects all re-read the level
from the row — a later message can never smuggle in more authority than
setup granted.
Enforced where it runs
A new central gate (lib/agent/authority.ts, deliberately pure) evaluates
every world-touching call at the same chokepoint as the planning/chat
hard-lock: file writes → edit, command text → shell, http → network,
git ops → read/git_mutation, MCP tools → subagent, preview spawns →
shell. The universal denies remain re-appended LAST, so a
grant-everything override still cannot run rm -rf / or dd to a device.
Repo-local git work (add/checkout/revert) gained explicit allow
rules at execute — without them the silent default would have refused
routine staging, contradicting v1.20's own promise that routine work
proceeds.
ask fails closed
A mid-flight run cannot stop to hold an approval conversation — no
per-action approval queue exists yet. So an unresolved ask rule now
REFUSES the call with a citation of the deciding rule and how to grant it
(raise the level, or add an explicit per-resource override). Silently
proceeding would make ask read as allow in practice — the exact
authority escalation this layer exists to prevent. The interactive
approval queue is follow-up work, stated in the README's honest-boundaries
section rather than implied away.
Secrets, now true at dispatch
The rules always said secrets "ask at every level". At dispatch they now
block: file_read('.env') is refused at all four levels with an approval
citation, while .env.example stays readable. Before this release that
promise was enforced nowhere — the file was silently readable at every
level. Behavior change, on purpose, disclosed in the PR.
The panel shows the policy that runs
The Work live Authority panel now derives its rows from the stored level:
read_only locks writes and commands outright, assist gates them,
autonomous allows them while push/publish stays gated and force-push
stays denied. Each row keeps its hover "why", so "Why?" is answerable
from the panel itself, not a docs page.
Verification
CI green on the PR and again on master after the merge (Verify: install,
typecheck, full suite). +40 dispatch-time wiring contract tests pin
normalize/reject inputs, per-level matrices, secrets-at-every-level,
override-vs-universal-denies ordering, and scope honesty. The two pure
modules (permissions.ts, authority.ts) additionally smoke-verified
under Node 24 type-stripping: 31/31 assertions. The quickest live proof
an operator can run: start a Work task at read_only, ask it to write a
file, and watch the refusal cite its rule.
Known boundary
Interactive per-action approvals do not exist yet — unresolved asks
refuse, they do not pause. GUI zone governance (v1.21's other pillar,
from b6bf1b8e) composes unchanged: physical acts classify into zones at
design time, and irreversible can never evaluate to allow.