Skip to content

fix: compaction indicator flicker + reasoning-fuel surfacing#96

Merged
m62624 merged 9 commits into
mainfrom
feat/reasoning-fuel-world
Jul 8, 2026
Merged

fix: compaction indicator flicker + reasoning-fuel surfacing#96
m62624 merged 9 commits into
mainfrom
feat/reasoning-fuel-world

Conversation

@m62624

@m62624 m62624 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

No description provided.

m62624 and others added 9 commits July 8, 2026 11:28
…port parsing

Introduce the plan's living logical world: a persistent registry of
model-asserted VRF statements compiled into a multi-domain program the
engine re-checks as a whole. Statements accumulate across stages, carry
file anchors that demote FACT→BELIEVES when the source changes, and the
directory always mirrors the registry.

Crucially, the store treats the engine exactly as planner_elenchus_check
does: it runs a program, scans the output for one of four verdict codes,
and hands the raw output to callers verbatim. It never parses the report
body — no orphans, beliefs, or derivations — so pi-planner grows no
dependency on the engine's private JSON shape for model-authored
programs. A source-level invariant test guards this: the module must not
reference any report internals. Everything a fuel/directive layer needs
beyond the verdict comes from the planner's own artifacts and its own
anchor hash sweep, never from the engine's output.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eb, zero engine parsing

Add the deterministic fuel scalar and its collectors. Fuel measures how much
of the interacting-condition web on the table the model actually ran through
the engine, and nudges only where a web exists:

  unmet   = max(0, warrantedWeb - coverage)
  fuel    = round(100 * (1 - (unmet+stale+friction) / (warrantedWeb+stale+friction+1)))
          = null  when there is no web, no stale anchor, and no friction

This yields the intended 2×2: skipping the engine where a web exists depletes
fuel in proportion to the ignored web (anti-laziness); running it where there
is none earns nothing and costs nothing (anti-ritual, no punishment). Coverage
is capped at the warranted web so a no-web run cannot manufacture credit.

Every input comes from the planner's own artifacts and records — branches from
the behavior board, constraints from the spec, shared surfaces from the task
graph, the verdict enum + gate-repeat counter from last-check, staleness from
the compiler's own anchor sweep. Nothing reads the engine's report. Engagement
credits only model-authored runs (a mechanical gate is the floor, not the
model's reasoning) and credits a CONFLICT as honest engagement — the existing
CONFLICT-block, not fuel, is what stops the step from advancing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…urface fuel, never parse the engine

Add the planner_reason wrapper tool with three modes over the slim world store:

  assert  — add statements to a domain and re-check the whole world; an
            observation may name a source file to anchor to (hashed here).
  retract — remove statements by id and re-check (the anti-deadlock escape,
            always applied; unknown ids are reported, never thrown).
  recheck — re-run the world as-is.

Every response returns the verdict code and the engine's raw output verbatim —
the tool parses nothing — plus the step's reasoning-fuel directive. Fuel is
assembled by reason-context from the planner's own artifacts (spec constraints
at consistency_check, task branches at the execution reasoning steps, shared
task surfaces at doubt_review) and rendered by reason-directive's tone ladder:
silent when no web is warranted, quiet at ≥70, naming the deficit below that.
The run writes a model-authored last-check record so fuel credits the
engagement; a CONFLICT still hard-blocks finish_step via the existing guard.

Gated exactly like planner_elenchus_check: registered in PLANNER_WRAPPER_TOOLS,
listed at the six reasoning steps in both tool-policy and stage-behavior, and
scoped into the broken-state repair_or_resume set. Dual-gate invariant and
tool-count tests updated accordingly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…er_status

Two pieces that surface fuel where the model reads it:

- writeElenchusLastCheck now carries a `repeat` counter: same gate + same
  sourceHash + same verdict in a row increments it; any real change (new
  source) or a different verdict resets it; a model-authored check never
  accrues one. This is the sole source of the gate-thrash friction signal —
  derived from the planner's own record, no new journal.

- planner_status renders a "## Reasoning Fuel" section at the reasoning steps,
  computed by the shared reason-context loader and rendered by the tone ladder:
  silent when no web is warranted, a single line at ≥70, the top deficit and a
  cheap move in 30–69, and the directing "what's needed / not needed" pair
  below 30 or on any friction. Fixed the ladder so any friction forces the
  directing tone even at otherwise-high fuel — a named pathology must surface.

Fuel remains tone-only: it enters no allow/block condition anywhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ecision

Enforce the load-bearing property structurally: the seven modules that decide
what is allowed, blocked, gated, or transitionable must not import a fuel
module, and only the surfacing layer (the reason tool and the status text) may.
A decision that cannot see fuel cannot key on it — so the only floors stay on
named terminal defects (a CONFLICT verdict, an un-CONSISTENT gate), never on
the fuel level. Importing a fuel module into any decision path breaks the test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…he DOX chains

Update the src/vrf and src/runtime AGENTS.md contract blocks: the living world
store (verdict-only, never parses the report), the fuel modules (pure math,
context loader, tone-ladder directive, planner_reason tool), and two Stable
Contracts — model-authored programs read only the verdict code, and reasoning
fuel is tone-only (enters no allow/block decision). Plain, factual entries in
the existing Domain Details style.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…on tail

A live taskdep run showed 0 planner_elenchus_check / 0 planner_reason calls
in ~1h across all reasoning steps: the fuel directive only rendered in
planner_status (called 4x) and the reason-tool tails (never called -> chicken
and egg), while the model reads planner_finish_step (78x) each step, where fuel
never appeared.

Surface it where the model actually looks: status.ts formatTransitionReasoningFuelTail
renders the fuel line for the step a just-applied transition lands on, and the
tool dispatcher (index.ts) appends it to the workflow-tool result. workflow-tools.ts
only passes planPaths through as data; the dispatcher imports the formatter from
the surfacing layer (status.ts), never a fuel module, so fuel-never-blocks holds.
Presentational only, self-silences when no web/friction is warranted.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
git inspection swells when generated bulk lands in the worktree untracked:
a fresh project with a missing/empty .gitignore, or an existing project
adopting a new tool whose artifact the current .gitignore misses. Add a
generalized planning note to account for these and add a .gitignore task
when the ignore rules are absent — without enumerating per-ecosystem names
the model already knows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… streams

Two bugs in the compaction banner:

- The animated percent/bar advanced off elapsed time, and the SDK's setWidget
  has no diffing (every push disposes and rebuilds the component), so each
  update tore the aboveEditor banner down and reflowed the editor — the banner
  vanished and reappeared on every percent, jumping the text. Make the rendered
  line fully static for the run: reason + size, and the predicted duration as a
  fixed ~ETA hint, never a moving bar/percent. The widget setter's content dedup
  now pushes it once and skips every later tick; the interval only enforces the
  safety deadline. Drops the now-purposeless progress-bar helpers.

- The banner did not clear while the model streamed its reply after a swallowed
  compaction: a streaming assistant reply arrives as message_update tokens (the
  signal the dashboard tracks), and message_start alone did not fire reliably
  for it. Clear the stale indicator on message_update too; the clear is
  idempotent so the per-token call is cheap.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added the fix label Jul 8, 2026
@m62624 m62624 merged commit e6544a9 into main Jul 8, 2026
6 checks passed
@m62624 m62624 deleted the feat/reasoning-fuel-world branch July 8, 2026 09:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant