v0.33.0
The performance & autonomy overhaul — the tool stops feeling slow and stalled. A cold
engineering review (three parallel deep-dives over the work loop, the plan→expert flow and the
architecture) found the stalls were structural: an unbounded CI wait, a 6-minute reviewer poll
paid on every PR, a dead-code run budget, no automatic epic progression, zero visible signal
when a run stopped, ~50–140 uncached GitHub calls per issue, a ~1.3 s interpreter spawn on every
tool call, ~72 KB of always-loaded instructions, and a telemetry subsystem blind to time. Epic
#561 fixed all fifteen findings: #562–#576, PRs #577–#591, every behavioral change with tests
verified by breaking the code, and every PR through the review gate (Codex external review in
rounds while quota lasted — 40+ findings fixed across the batch — then the sanctioned
self-review fallback, stated on each record).
Added
- The product agenda policy is written down (#576, closing epic #561's list).
CONTRIBUTING.md
now states the rebalance rule the architecture review argued for: releases should carry
user-facing value (the last measured week ran 8:1 self-directed); before building a new
self-control mechanism, ask what measurement would make it unnecessary; and field evidence
(/board telemetry, now with durations) outranks introspection when picking the next piece
of work. A policy, not a gate — deliberately unenforced.
Changed
Board-Work.ps1sheds its inline job scheduler and process killer (#575, part of epic
#561; first slice of the monolith split). The 3,176-line dispatcher — 18.7% of all script
code — contained a machine-capacity governor and a process-tree supervisor a board tool has
no business holding inline. Both clusters moved verbatim into part files
(BoardWork.Capacity.ps1: capacity snapshot, dispatch plan, fleet slot pacing, dispatch
governor;BoardWork.Processes.ps1: pid→parent map, descendant walk, guarded tree kill,
orphan detection and reaping), dot-sourced before the test guard so every caller and test
sees the exact same surface. Behavior-preserving by construction and by evidence: 371 tests
across the Board-Work, Board-Doctor and lint suites stay green with zero changes to them.
Further slices (session registry, worktree launch) can now follow the same pattern.
Added
.agentic-board/finally has a garbage collector (#574, part of epic #561). The state dir
had accumulated 48 files across the project's entire life — briefings and launch scripts for
issues #123→#532, signal markers, compaction snapshots — because nothing ever cleaned it.
Clear-AbiosState.ps1reaps only regenerable per-run debris (briefings, launch scripts,
expert briefs, per-issue logs, signal markers, snapshots) under three pinned rules: a live
session protects its files at any age, files younger than-MaxAgeDays(14) stay, and durable
records (sessions history, run-ledger marker, contract, denial log, fleet files) plus anything
unrecognized are never touched — unknown is somebody's state, not deletable. Plan-only by
default,-Forceexecutes, every reap entry carries its reason. This is the GC half of the
"one run, one record" direction; the record half was seeded by #568'ssessions-history.jsonl
archive (started/ended/outcome). Verified by 9 Pester tests.
Changed
- The always-loaded instruction payload shrank ~60% (#573, part of epic #561). Every
/boardinvocation loaded ~72 KB of instructions (≈18k tokens, ~9% of a 200k window) before
reading a single line of the actual issue —commands/board.mddescribed all 22 verbs in full
andprojects-admin/SKILL.mdduplicated the three biggest. The ten heavyweight verbs (work,
plan, fill, field, changelog, handoff, doctor, cerrar-ciclo, telemetry, triage) now live in
per-verb reference files loaded on demand when that verb runs; the command keeps the menu,
one-line routing and the safety rules, and the skill keeps identity/anchoring/conventions and
the routing table. board.md: 34.7→9.7 KB; SKILL.md: 31.6→12.7 KB. No content was deleted —
every recipe moved verbatim into its reference. Contract intact: command-surface suite green
and README regions unchanged (docs-freshness gate). This weight was causing the very
auto-compactions the run-ledger machinery exists to survive — paying the disease and the cure. - The brake hook stops taxing every tool call in every session (#572, part of epic #561).
The PreToolUse hook fires on every Bash/Edit/Write in every session and paid a full pwsh
interpreter spawn (measured ~1.3 s on the reference machine) just to discover that no
brake marker exists — the case for every session except a launched autonomous run. At 100
tool calls per session that was ~2 minutes of pure interpreter startup, paid by every user in
every repo, and the tool had no way to see it (#568's blindness, costing #572's tax).
Brake-PreCheck.cmdnow answers the common case in ~0.25 s (5× cheaper): no marker in
the working directory or three ancestors → exit 0 without ever starting pwsh. Fail direction
unchanged: an armed worktree always reaches the real PowerShell guard with its fail-closed
behavior intact, and stdin passes through untouched. Verified by 4 new tests, including an
end-to-end deny through the shim over real stdin and a no-pwsh timing bound. - GitHub reads gain a cache, and the fail-closed wrapper gains a lint that stops the
bleeding (#571, part of epic #561). Every gh invocation is a fresh process + TLS round trip
and nothing was cached — a clean work cycle made ~50–65 calls, many re-reading facts that had
not changed.Invoke-GhCachedis a small cross-process file cache (in-memory caches die
with each script's pwsh process) for read-only queries with an explicit TTL; failures are
never cached,-Forcebusts, TTL 0 always fetches. The board's Status-schema read — the
exact anti-pattern quoted in the wrapper's own header, still live — now goes through it
(5-min TTL), and four more of the worst raw sites migrated toInvoke-Gh(issue blockers,
claims, linked PRs, commit search: a 401 no longer reads as "no blockers/no claim/no PRs").
A new lint test freezes the per-file raw-gh baseline (59 sites): counts may only go down,
a new raw call names its file and fails, and stale allowances are themselves flagged.
Verified by 7 new Pester tests (cache semantics + lint). - Evidence is written once, linked twice (#570, part of epic #561). The same evidence block
used to be copied to three destinations (PR body, issue comment,evidence/<issue>.md) —
three copies of one content, drifting independently, and the "INCOMPLETE → record → re-run"
loop mostly existed to keep them in sync. Nowevidence/<issue>.mdis the single source of
truth and the PR body and issue comment carry a link stub
(Format-EvidenceLinkStub: marker + pass/fail summary + link to the file). The completion
check tightened accordingly: a surface satisfies its requirement with the marker plus
substance (the file reference or a full block — pre-#570 runs stay valid); a bare marker is
a stamp anyone can leave, and now reads as missing. Verified by 7 new Pester tests.
Added
- The definition of done scales to the diff (#569, part of epic #561). The contract's six
gates all defaulted totrueand the brief listed them flat, so a 10-line docs fix owed the
same verify pass as a model migration.Get-ApplicableDodGatesderives the owed gates from
the changed paths —bpa/tmdlBreakingonly when a semantic-model file changed,
build/lint/testsonly when something executable changed,cialways — and the
work-class CLI prints which gates the current diff owes and which it does not. Fail direction:
an unreadable diff owes every enabled gate, the derivation can only disable gates the
contract enabled (never re-enable one it turned off), and an unrecognized custom gate is
always owed. The autonomous brief now points the run at this instead of the flat list.
Verified by 6 new Pester tests. - The tool can finally measure its own time (#568, part of epic #561). The telemetry
subsystem measured everything except time: the transcript parser discarded the timestamp
every line carries, the correlation window was "6 events" (4 seconds or 40 minutes,
unknowable), the ledger's columns were events and bytes,startedhad minute granularity, and
completed session rows were deleted — every run's wall-clock cost vanished at the moment it
finished. Now: events keep their ISO timestamp; episodes carryts+durationMs(unknown
staysnull, never a fabricated zero); per-session records and the field ledger gain
durationMin;startedis stamped to the second (the supervisor reads both formats); and a
completed session row is archived tosessions-history.jsonlwithendedand its outcome
(pr-merged / cleaned / close-loop) before removal. Verified by 8 new Pester tests. - The visual work-class gate scopes to what the owner actually judges by looking (#567, part
of epic #561). In a web app (a FabricApp) every change touches html/css/assets, so the
classifier marked the whole project "visual" and the classification stopped carrying
information — when the end-to-end gate reopens (#541), that would have routed 100% of web work
to the human. The policy gainsworkClass.codeExceptions— globs subtracted from
visualPatterns, declared per project in the contract (e.g.src/components/**/*.cssfor
utility styles nobody screenshots), never guessed: the default list is empty and the
fail-visual direction is unchanged out of the box. And the verdict now carries
visualGroups— visual paths grouped by top-level section — so the owner approves "the
pages section" once instead of 47 file rows. Verified by 6 new Pester tests. - The epic walker:
/board expert auto -Epic <n>dispatches sub-issues wave by wave (#566,
part of epic #561). Nothing advanced an epic before — Expert-Auto took one-Issue, so a plan
with N sub-issues cost N human launches (Board-Plan even fetched the sub-issue list and threw
it away).-Epicreads the epic's native sub-issues, classifies them (done / in-flight /
blocked / ready) and launches one autonomous session per ready sub-issue with the
contract's brake and budget, each briefed with the epic's enriched plan plus its own issue
text. Idempotent by design: merge a wave's PRs and re-run the same command for the next
wave; done and in-flight sub-issues are never re-dispatched, and a sub-issue whose PR state
could not be read counts as in-flight — dispatching a possible duplicate session is the worse
error, so the unknown fails closed. Verified by 7 new Pester tests on the wave classifier. - A stopped run now signals instead of sitting silent (#565, part of epic #561). A braked,
out-of-budget, or stalled run was invisible from outside — the deny payload reached only the
model, the supervisor's verdict existed only on a terminal nobody was required to watch, and
the human learned about a stall by noticing the PR never appeared. Three surfaces fix that:
every hook denial appends to a localdenials.jsonland posts one[abios-signal]issue
comment per (kind, issue) — deduped by a marker file so a retrying agent cannot flood the
issue (the brake marker now records the repo to make posting possible); the fleet supervisor
gained-Post, publishing an[abios-stall]comment for each session past the threshold
with no PR; and the session watch runs the supervisor automatically every 10 poll cycles,
so stalls surface without a separate human command. All signaling is best-effort and can never
change a verdict. Verified by 14 new Pester tests across the guard, supervisor and watch. - The expert run's time budget is now enforced, not advisory (#564, part of epic #561).
Get-BudgetVerdictcomputed a handoff verdict that nothing ever called — the contract's
120-minute budget existed only as a sentence in the brief, so a runaway run had no wall-clock
limit at all. The budget now travels in the brake marker (budgetMinutes+armedAt) and the
PreToolUse hook enforces it: past the budget, work commands are refused with an instructive
message while the wrap-up stays open —/board handoff -Save, committing and pushing WIP,
closing the run ledger, and leaving a PR/issue comment. The brake always wins over the
exemption (a push tomainover budget is still a refused merge), and the budget fails
open on a corrupt timestamp — it is a liveness limit, not a safety control, and must never
brick ordinary work. Iteration counts stay advisory: a hook that sees single tool calls cannot
count verify-loop iterations honestly. Verified by 24 new Pester tests (pure + hook end-to-end
over real stdin); mutation-checked (enforcement disconnected → 3 tests red → restored).
Fixed
- The review wait learns from silence, and any reviewer's answer counts (#563, part of epic
#561). Two defects made every PR pay the full review timeout forever: the wait loop broke only
on a review whose author was Copilot — a human or external review landing mid-poll kept it
spinning — and the per-account cooldown (#367) armed only on an explicit "cannot review"
answer, so a Copilot that was simply silent taught the gate nothing. Now the wait's arrival
test is the same evidence rule as the verdict — any GitHub review or recorded external
review ([abios-review]comment) bound to the current head commit ends it (stale evidence of
earlier commits keeps waiting), the PR comments arrive in the same authoritative GraphQL read
as the reviews (one call fewer per poll), and silence past the timeout arms a
1-day cooldown (weaker evidence than an explicit refusal, which keeps its
-CopilotCooldownDaysdefault of 7). Verified by 10 new Pester tests; mutation-checked
(author-only arrival and inverted silence check reintroduced → 5 tests red → restored). - The review gate's CI wait now has a ceiling, and the CI and review waits run concurrently
(#562, part of epic #561).gh pr checks --watchwas the only unbounded wait in the codebase:
a queued or never-scheduled workflow hung the entire session indefinitely, with no signal. The
gate now polls a structured checks snapshot in the same loop as the review wait — worst case is
max(CI, review) instead of their sum — and the CI side expires at-CiTimeoutMinutes
(default 25) into an explicit "checks still pending" BLOCK, never a silent hang and never
a pass. An unreadable checks snapshot also blocks (fail closed) instead of being trusted from
display text. Verified by 12 new Pester tests; mutation-checked by reintroducing both defects
(cancel-counts-as-pass, sequential-OR exit) and watching 4 tests go red.