Commit graph

6 commits

Author SHA1 Message Date
Ouroboros
98ffcf2a62 Size the automatic context-reclaim pass to a low-water margin
The trigger is unchanged: a positive deficit against the binding boundary
(the smaller known of owner target and route capacity), one pass per
route+round. The requested goal becomes deficit +
ceil(boundary / RECLAIM_LOW_WATER_DIVISOR), so a pass lands about an eighth
of the boundary below it instead of exactly at it, where the next round's
ordinary growth re-armed it nearly every round. RECLAIM_LOW_WATER_DIVISOR = 8
is a documented structural constant in context_budget (not a setting; pinned
by test_context_budget_ssot so it stays a one-constant change).
MainFitMeasurement gains low_water_margin_tokens, appended last.

The context_reclaim checkpoint event records deficit_tokens,
requested_margin_tokens, achieved_headroom_tokens (the landing re-measured on
the same fit basis as the trigger), boundary_reached, below_boundary
(reclaiming exactly the deficit reaches the boundary, it does not get below
it) and rounds_since_previous_pass; an unmeasurable landing is reported as
unknown, never as "not reached". The actual-overflow path requests the same
low-water minimum instead of a token-sized pass before its one strict-shrink
retry. Materializer, receipts and the route+round latch are untouched.

Tests: margin arithmetic in both directions (binding boundary, no deficit,
divisor as the one knob), the anti-thrash class test with the worst-case
full-budget summarizer stub (4 passes in 40 rounds against 39 with the
margin removed; the stub halves every selected unit, so the asserted bound
is ceil(N*g / (margin/2)) + 1 plus the achieved-headroom form), the
checkpoint telemetry, the unmeasured landing, and the overflow-minimum pins.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-26 08:16:04 +03:00
Ouroboros
42e04e2e76 Preserve prompt prefixes when appenders lack send knowledge
Merge user content only when the existing observed-send record proves the tail absent. Keep recorded messages intact for slot-less producers and verify repair, multipart and Local/GigaChat request shapes.

Local Phase B checkpoint; complete phase review and publication are pending.
2026-09-18 04:40:34 +03:00
Ouroboros
0988f4ba90 Mint one issuer fact and stop task-authored messages travelling as owner text
The 14.09 incident: a Project root steered the Main root and its words arrived
as `[Message from my human]`. `_handle_steer_task` decided WHO was speaking five
times from proxies -- `allow_global_root` derived from a routing contract a
Swarm-admitted root never has, the room veto keyed on the chat id, the owner
notice inverted on an empty client id, every steer written as KIND_OWNER_TEXT,
and the owner-message generation bumped on each -- so a task's words entered
the owner-directive corpus and superseded a paid acceptance panel.

The host now mints ONE issuer fact by value where the client surface is minted
(`control_routing._routing_issuer`): an owner turn (a direct chat turn, or a
task relaying the owner message it just drained -- the #896/#900 substitution
and receipt key are untouched) or a task speaking for itself. The model has no
argument to claim otherwise; `allow_global_root` and its `source_lane` reading
are deleted, and the picker click carries the owner_turn issuer instead.

Owner turns keep today's path. A task's own words are written through the one
task-message writer `forward_to_worker` also uses, with the new closed
provenance value `independent_task` landing at its three seams in this change:
the writer set, the render ladder (`[Message from independent task <id>]`,
never the ancestor fallback) and the drain mapping (context the receiving
model judges -- no owner corpus row, so owner_source_sha256, the post-drain
growth check and the acceptance premises stay the owner's; owner 4=A). Any
host-listed active independent root is addressable (owner 6C; hidden-partition
roots included, no room veto), no chat is told, no attachments ride, and the
issuer is told WRITTEN or refused with the host's reason under the act's own
synthetic receipt id; a `task_message_routed` row and the receiver's
`task_message_injected` row make author and target visible in Logs (owner 5=A).
The owner-turn veto is computed from the registry lane of the issuing chat
(Main sees every root; a Project room its own), the same rule the picker
click and a legacy issuer-less event read.

Roster for 6C: pooled roots from the queue snapshot, direct roots from a small
off-lock fragment the main loop writes (`supervisor/direct_roots.py`, actor
locks only tried, one aggregate incompleteness fact, cleared at queue init);
an independent root receives a `[INDEPENDENT_ROOTS]` TAIL note only when the
roster changed, after the round's reclaim and before the observation and seal.
The #929 carve-out is generalized into one predicate
(`transcript_prefix.sent_in_previous_send`): no producer merges into a row
the previous send already carried.

Tool schemas: promote says it starts a NEW task and points at
ensure_project_scope for moving THIS task; steer says any host-listed root and
how the message lands per issuer; ensure says it binds durably and reports the
real outcome. The chat-id lint allowlist names the three rewritten sites.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 17:54:03 +03:00
Anton Razzhigaev
476b18a5c4 Keep run_llm_loop at the function gate and never let a compaction stamp explain a system-row rewrite
Review round 2 (Fable lane): the relocated hook grew `run_llm_loop` from 300
to 304 lines (a comment block that restated the helper's docstring), which the
size-ratchet lane refuses as new function debt; the comment is gone and the
explanation lives in `_record_transcript_prefix`'s docstring. On the
overflow-recovery path one model call can both reproject the system row to
Low and compact the history, so the single compaction stamp would have
sanctioned the `system_rewritten` fact; `observe_send` now lets a stamp
explain a break below the system row only (compaction never touches the
system row, a context-fit reprojection touches nothing else), which makes the
documented "reprojection is an ordinary break" exactly true. Unit test added.

Correction to the previous commit's wording: of the two seam tests only the
automatic-reclaim one was red on the round-1 candidate; the manual-compaction
test was green there because that candidate sanctioned the manual path from
its usage, and it fails when the manual seam's stamp is removed.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 05:37:56 +03:00
Anton Razzhigaev
3515e2d30d Observe the dispatched transcript and let the compaction seams stamp their sanction
Review round 1 (Fable lane) showed the recorder taking its digest snapshot
before the model call and learning "sanctioned" only from the manual reclaim's
usage, so the automatic main-fit reclaim that runs inside `_call_round_model`
was reported one round late as an unexplained `prompt_prefix_break` and counted
in `prompt_prefix_breaks`.

- The observation now runs after the round's model call and fallback chain,
  before the assistant row is appended, so it compares what round N actually
  dispatched with what round N+1 dispatches and `round` names the send that
  broke.
- The three compaction seams that rewrite sent history on purpose (automatic
  `_run_main_reclaim`, manual `_run_round_compaction`, the authored context
  view) stamp a one-shot `transcript_prefix.sanction_rewrite(ctx, "compaction")`
  that the next observation consumes; the hook no longer reads the manual
  path's usage.
- Two loop tests drive both seams through a faked summarizer and pin
  `sanctioned_by == "compaction"` on the compaction round with no unexplained
  count (both red on the previous candidate); a unit test pins the one-shot
  stamp. Docs and docstrings say what the code does.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 02:15:42 +03:00
Anton Razzhigaev
373520bfd4 Keep the acceptance observation append-only so OpenAI-family prompt caches extend
Since 2026-09-13 the main loop appended a fresh `[ACCEPTANCE_SUBJECT_OBSERVATION]`
user row to the end of every model request and removed the previous one on the
next round. The rendered facts carried the per-round `tool_count`, so no request
was ever a byte-prefix of the one before it. OpenAI-family caches (the Codex
subscription backend, the OpenAI API, OpenRouter -> OpenAI) reuse a previous
request only when that whole request is a prefix of the next, so the
conversation cache never grew past the first round: 71.5 % token-weighted cache
hit on the codex main lane on 2026-09-14 against 96-100 % for children without
the row, about 21.8 M uncached tokens in one day (issue #906).

- `prepare_acceptance_observation` appends a new observation row only when the
  rendered owner-source facts changed and never removes or rewrites an earlier
  one; `acceptance_observation_prompt` renders the facts without `tool_count`
  (the value stays on the stored observation for delivery); the owner-message
  merge never folds text into an already-sent observation row.
- New leaf `ouroboros/transcript_prefix.py` records the append-only invariant
  between the sends of one execution: per-message content digests and a
  `prompt_prefix_break` task_checkpoint fact (kind, index, sanctioned_by) hooked
  after `seal_task_transcript`; compaction is the sanctioned rewrite, every other
  break is counted in the loop usage as `prompt_prefix_breaks`. It records,
  never blocks.
- `tests/test_transcript_prefix.py` pins the invariant on the real
  `run_llm_loop` (red on the previous tree, exactly at the replaced observation
  row) plus the compaction-sanctioned disclosure;
  `tests/test_acceptance_observation_append_only.py` pins the row semantics.
- DEVELOPMENT.md cache-friendliness invariant and ARCHITECTURE.md module map
  describe the rule; domain inventory regenerated for the new module.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 01:44:58 +03:00