5 Commits
Author SHA1 Message Date
esengine 3d0fe85e7e refactor(event): remove the readiness and audit channels nothing sends to
With the final-readiness gate gone, FinalReadinessError, the readiness audit
and the structured readiness on TurnDone have no producer, and neither do the
contract-shadow, completion-report, evidence-bundle, outcome-progress,
protocol-recovery, verification-drift and project-check-probe audits. Removes
the types, the sink capabilities and their forwarding in every wrapper, the
trajectory and metrics fields, the final_readiness outcome and notice code,
and the delivery-recovery submit port.

The ACP status shape and the desktop reader of old sessions keep their
readiness fields: external editors and recorded sessions still carry them.
2026-11-02 05:59:36 -08:00
yhh ad8b9c71ff fix(agent): tell the model when a reasoning-only response was cut off at a limit 2026-10-11 01:11:34 -07:00
Stephen R. van den Berg 291a03a9ca fix(agent): detect perseveration by default, reported via progress watch
The guard detects a degenerate generation loop — a model echoing one short block
of text or reasoning byte-for-byte — by default, and reports it through the
progress-watch channel as a new `perseveration` cause. Nothing is cut and nothing
reaches the model unless the caller opts in: a byte-identical block is sometimes
legitimate output (printing a value 300 times, a run of identical table or log
rows, a page of `=` or `A`), so cutting it by default was the wrong trade. The
notice is said once per loop episode — a scan that trips again while the previous
scan also tripped stays quiet, and the next scan that does not trip re-arms it.
The user's `progress_watch.pause` setting is what ends a run on a detected loop.

Cutting the stream and nudging an unattended retry is opt-in with
`perseveration_retries`, under [progress_watch] or per provider on a [[providers]]
entry (the provider value overrides the global default). Unset or 0 reports only;
a positive value also cuts and nudges up to that many retries; a negative value
disables the guard entirely.

The cut path records best-effort usage, lets extensions rule on the terminal
before it is persisted, and never executes the aborted stream's tool calls. It
collapses the loop to the phrase and its last repetition in what is shown and
stored alike, so the transcript follows the trim instead of silently diverging
from the stored turn — no history rewrite is needed. It then shows the user a
`[retrying (N) avoiding perseveration]` interjection, appends a host-authored
nudge through the mid-turn steer path, and retries. Once the budget is spent the
run stops with a typed perseveration pause and emits the `perseveration_loop`
notice, so a parent cannot mistake the loop for a clean result. The retry budget
lives on the run, so the next user message starts fresh.

Adds a boot-level effect test asserting what reaches the provider request and the
frontend sink, and updates the guide.
2026-09-29 02:55:50 +02:00
esengine f229559c8f feat(studio): say when a run stops producing observable progress
A run that keeps calling tools without changing anything the host can
observe (#11153: dozens of successful `echo` calls with varying arguments,
about 1.1M input tokens) had no signal short of reading the transcript.
This adds one, without bringing back any round-count guard: nothing is
sent to the model, no call is refused, and the cache-stable prefix and
the per-turn execution-policy block are untouched.

Judgement (kernel, structure only):
- evidence.EffectWatch scores each round's receipts. An effect is a
  successful mutation or named write, a task step moving (StepProof,
  TodoStep, or the host's todo progress counter), a verification whose
  outcome changed since it last ran, a first read (a read-only tool
  answering a request not seen before in this run, or a shell command
  naming an existing path not named before, resolved by stat), or a
  delegated result coming back. A command merely being new is not.
- agent.settleProgressRound runs at each tool-round boundary; the count
  lives in turnRuntime, so a user message zeroes it. A second,
  model-independent backstop compares the input tokens spent since the
  last round with an effect against context window x N; an effect
  restarts both counts, so a long run that keeps doing observable work
  is never flagged however much it spends. Without a ledger the round
  judgement stays silent rather than guess.

Feature 2 (default on): a content-free progress_watch event carries the
reading; Studio shows a one-line strip above the composer with Stop
(the ordinary cancel) and "don't show again (this session)". It clears
itself when an effect lands or the turn ends. The TUI says it once per
stall.

Feature 3 (default off): [progress_watch] pause = true ends the run at
the round boundary through a resumable pause (RunPauseInfo Kind
no_progress, TurnDone outcome no_progress). No finalization nudge is
injected. Continue sends an ordinary message, which starts a new run
with the count at zero. The section is user-level only: load restores
the user's value over a project reasonix.toml. Settings > Session edits
it through GET/POST /progress-watch, which persists to the user file
and hands the new watch to the running executor without a rebuild.
Out-of-range values refuse with progress_watch.out_of_range.

Refs #11153
2026-09-28 01:28:33 -07:00
yhh 394da0a313 refactor: the kernel's packages live in the layer they belong to
internal/ was 141 packages side by side, so nothing but a reader's memory
said which were foundations and which assembled the rest. Each now sits in a
layer directory, and the directory is the declaration:

  base      L0  foundation with no domain in it
  contract  L1  types and interfaces the layers above agree on
  model, tools, safety, ext, state, platform
            L2  subsystems, peers of one another
  runtime   L3  the turn engine
  session   L4  the controller
  assembly  L5  the composition root
  frontend  L6  cli, serve, acp and the host adapters

Only paths change: package names, code and behaviour do not. Imports,
repolint and coverage baselines, sensitive paths, CI package lists and docs
follow the new paths; release notes keep the paths they were written with,
and sample paths inside tests and fixtures keep theirs.

repolint gains layer-order: a package may import its own layer and those
below it, read from where it lives. The 47 imports that point upward today
are recorded as that rule's opening debt (-allow-widen, a new rule's first
baseline) and can only go down. Most of it is config, which still reaches
into eight subsystems and is the next thing to take apart; the rest is
event, history and a few tools reaching into runtime for types that belong
in contract.
2026-09-23 18:28:21 -07:00