Compose the retained PR1778 fixes with published 1b425bfd. Preserve existing attribution and failed qualification evidence.
Reviewed exact contribution: rl-20261015T123130-e0b2b17fb766 (Astra, Opus, Grok code and coupling). No tag or release qualification implied.
Preserve the selected 9419 cut and the existing PR1778 repair history.
Complete pre-paint composer fitting without cancelling owner selection
motion, and retain pytest subtest failures without counting them as
repeated parent executions. Keep the reporter usable with older pytest.
Separate required and optional provider qualification as explicitly
chosen by the owner; both producers remain strict and optional failures
stay visible. Synchronize the 7.7.0 carriers and preserve contributor,
migration, rollback and known-limit disclosures.
Isolate synthetic staged-book fixtures from the real index. Start the
async contention oracle at sender readiness; its new 10s preparation
bound is separate from the unchanged contention/custody assertions.
The historical preparation delay remains unattributed.
Review: Fable whole change/coupling PASS (rl-20261013T024153-3f2eb954cb5c);
Grok whole alternative review PASS (a1389e7f); Astra whole review raised
C1/C2, closed by exact delta review97571944; Opus230b7366 checked the
seven-line contention fixture correction. Configured quorum failed;
these are retained substantive whole/delta reviews, not an invented
configured PASS. Prior full REDs and current focused proofs are preserved;
exact committed qualification follows. No tag is created by this commit.
Every version carrier moves to 7.6.0 through ouroboros.tools.release_sync:
VERSION, pyproject.toml and uv.lock, web/package.json and its lock, the
gateway contract version, the README badge and download links, the two
install pages and the architecture header. The README Version History
gains the 7.6.0 row, which also generates the release notes, and the
oldest minor row (7.1.0) moves to the retained-in-Git sentence, keeping
the history within its limits (2 major, 5 minor, 5 patch rows).
The row discloses that the old dialogue memory is imported one way:
rolling the code back to 7.5.1 does not bring back what the new memory
recorded after the update. Its claims were checked against the code and
the commits since v7.5.1 by a separate review row, not against PR bodies.
Claudexor platform gate (API keys — subscription auth NOT covered) / toolchain · macos-latest · real managed Node/npm, no ambient Node, no harness install (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / toolchain · ubuntu-latest · real managed Node/npm, no ambient Node, no harness install (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / toolchain · windows-latest · real managed Node/npm, no ambient Node, no harness install (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / consumer · windows-latest · real pinned Codex local install + doctor, no login, no model task (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Has been cancelled
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Has been cancelled
CI / build (dmg, macos-latest, macos-arm64, syft_1.50.0_darwin_arm64.tar.gz, syft, e32fdb9d47823fa633748a1efca2528fd77c37469ea93c9e40ab835da44e4cce) (push) Has been cancelled
CI / build (tar.gz, ubuntu-latest, linux-x86_64, syft_1.50.0_linux_amd64.tar.gz, syft, bf7b29ff57f06da30918266a0e1c2885a8f99784798d1bdb1628886aa015d788) (push) Has been cancelled
CI / build (zip, windows-latest, windows-x64, syft_1.50.0_windows_amd64.zip, syft.exe, 815ee6973ec5dff6a671d7f41b0e78835a8c45b91d5a39f4743ea1cee833d3be) (push) Has been cancelled
Integrate the reviewed contribution and synchronize release carriers with the cumulative changes since 7.4.11. Keep the owner-approved Windows artifact download limitation explicit.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Waiting to run
Integration commit for the safe-check sprint (#1215#1143#1084#1112), merged as a057c5a4: bumps every version carrier to 7.4.10 and adds the README history row (the 7.4.5 patch row leaves the five-row window per P9).
Integration commit for the presentation batch (#1011#1087#1110#1154#1061#931#1073#516#869#498), merged as 553d5ac3: bumps every version carrier to 7.4.9 and adds the README history row (the 7.4.4 patch row leaves the five-row window per P9).
Integration commit for @komsikov's .gitignore change (#1253): bumps every version carrier to 7.4.8 and adds the README history row (the 7.4.2 patch row leaves the five-row window per P9).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
* fix(v7.2.2): preserve host message authorship through chat and replay
Host-composed command replies, runtime diagnostics, admission and lifecycle
notices now carry role="system" and a descriptive system_type at their
producer; send_with_budget persists both on progress records and history
replays the recorded voice, so a reload no longer re-narrates host text as
Ouroboros. Model narration, proactive replies, final answers and question
pointers stay model-authored, selected by the existing narration fact rather
than by reading text.
The Host Service named-operation view accepted only direction="out" terminal
rows, so a typed command reply would have stayed pending forever; it now
recognizes either output voice while keeping the exact-origin ownership check.
tests/test_host_message_voice.py scans runtime send calls and literal chat
envelopes with counted, reasoned model/transport exceptions, so a new
unstamped producer or a stale exception fails; computed aliases remain a
documented review duty. The complete personal census of 92 classified sites is
retained as task artifact host-message-census.json.
DESIGN defines authorship, the System row and the markdown asymmetry;
CHECKLISTS item 30 now covers backend UI-message producers, which closes the
stale pointer in the design-system chapter. Pre-existing unkeyed live-header
clearing and the untyped terminal-host-notice contract are unchanged.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
* feat(v7.4.0): add honest desktop notification attention cue
Add an optional launcher request_attention bridge that raises the live desktop window and requests one platform system sound, preserving browser and in-app fallbacks. Document the capability boundary, bump release carriers, and add focused tests.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
---------
Co-authored-by: Ouroboros <ouroboros@local.mac>
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
While a client runs, Ouroboros can now reach the owner who left the window.
Settings -> Appearance gains a notification block beside the theme: a master
switch, the two required categories (a question or decision is waiting for you,
a task finished or stopped), a model-chosen category for messages Ouroboros
sends while it works, a separate off-by-default toggle for ordinary replies in
Main, sound, an off-by-default show-the-text choice, and a test notification.
The choices are stored per client exactly like the appearance choice, never
reach the server, and are excluded from the settings-dirty tracker so a
client-local toggle never offers to discard unsaved server settings.
Delivery is page-level: a system banner where this client exposes one and
permission is granted, otherwise the in-app surface plus one short tone, with
the status line stating which surface this client actually has. Either way a
click opens the source. No OS permission, Do Not Disturb or platform limit is
bypassed, and no native launcher path is added in this version.
web/modules/notifications.js keeps classification and the delivery gate pure
over one live frame plus the stored preferences, and takes ONE subscription per
client on the shared socket. It is deliberately NOT wired inside a chat
instance: an instance dies with its room, so a Project the owner never opened
would never have rung - the case notifications exist for. web/modules/chat.js is
byte-identical to before. Only live frames reach the notifier, so a reload
cannot re-notify without storing any notification state.
A finished task is recognised on the shape it actually arrives in (the live
task_done log frame) as well as the authored summary, and a conversation turn's
own ending stays the ordinary-reply category instead of claiming a task
finished; all of them collapse to one key per task. A child task never notifies
the owner: it escalates to its parent, and lineage is read from the delegation
facts frames carry because the terminal frame carries none. Importance needs no
new host field and no second model call - the existing proactive-message
discriminator is the signal.
Policy has one canonical home in docs/DESIGN.md section 9, including its
disclosed limits; docs/DEVELOPMENT.md carries the engineering invariant and the
architecture chapter names the owner. Two chapter byte budgets are raised with
stated reasons.
Verification: 1506 node tests; four browser journeys driving real socket frames
through ws.js (defaults off, one notification per event, silence on reload,
child conclusion withheld, unopened Project room reached, machine traffic
refused, click moves to the source); the deterministic repository gates (size
ratchet, generated manifests, README history limit, chapter budgets, docs sync)
pass. Three failures in the full battery are proven pre-existing by running the
same tests on the unmodified base commit.
Review-driven corrections in this commit, each with its own regression: a
`task_done` frame counts as an ending only with a SETTLED status, because an
update or restart teardown reports `interrupted` and requeues the same task id
(ringing there would also have burned that task's key and silenced its real
completion); a bare `task_terminal_status` chat row belongs to a DIRECT turn and
stays the ordinary-reply category instead of claiming a task finished; a child's
chat row is dropped whatever else it carries; the room gate mirrors
`mainThreadAccepts`, so an external owner transport notifies while the hidden
partition and A2A ids never do; and the client-level listener absorbs its own
errors, because it runs before the chat instances' handlers and `ws.emit` does
not isolate them.
Version carriers synced by ouroboros.tools.release_sync from VERSION:
pyproject.toml, uv.lock, web/package.json, web/package-lock.json,
web/modules/api_types.js (GATEWAY_CONTRACT_VERSION), README badge and direct
download links, docs/ARCHITECTURE.md, docs/install/index.html and
site/install/index.html. README Version History gains the 7.2.1 row; the
oldest patch row (6.113.1) leaves the table and is named in the "older
releases" sentence, keeping the five-patch-row limit.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Version carriers synced by ouroboros.tools.release_sync from VERSION:
pyproject.toml, uv.lock, web/package.json, web/package-lock.json,
web/modules/api_types.js (GATEWAY_CONTRACT_VERSION), README badge and direct
download links, docs/ARCHITECTURE.md, docs/install/index.html and
site/install/index.html. README Version History gains the 7.2.0 row; the
oldest minor row (6.110.0) leaves the table and is named in the "older
releases" sentence, keeping the five-minor-row limit.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Synchronize every release carrier to 7.1.0 through release_sync: VERSION,
pyproject.toml, uv.lock, web/package.json, web/package-lock.json,
GATEWAY_CONTRACT_VERSION, the README badge and direct-download links, both
install pages and the ARCHITECTURE header. Add the 7.1.0 Version History row
for the work accumulated on `ouroboros` since v7.0.0 and roll the oldest minor
row (6.109.0) off to respect the P9 cap.
The reference-book migration test pinned the ARCHITECTURE h1 with its
split-time version token. That h1 is a declared release carrier, so the pin
now keeps the whole line and lets only the version token follow VERSION.
The books were one physical file each: ARCHITECTURE 2,386 lines and
DEVELOPMENT 3,886, with 13 and 14 `##` sections. `reference_books.py` had
shipped the chaptered reader — membership, authored introductions, exact
physical source views — with the migration still at zero, so every reader
took the legacy monolith branch and the validator had no production caller.
Each `##` section is now one chapter file under `docs/architecture/` or
`docs/development/`. The only new bytes per chapter are its prologue: the old
section title at H1 (numbering text kept, so every `ARCHITECTURE "8. Git
Branching, CI, and Build"` cross-reference still reads) and one authored
introductory paragraph saying what the chapter owns and why it exists.
Everything after that prologue is the old section body byte for byte, with
`###`/`####` levels untouched — so the residue rules keep reading the exact
subsection headings they exempt, and no section title was renamed.
The move is therefore INVERTIBLE, and `tests/test_reference_book_migration.py`
inverts it: drop each chapter's H1 line and its one introduction, re-prefix
`## `, concatenate in membership order, and require the recorded SHA-256 of
the old body — plus, whenever the base commit is reachable, byte equality with
`git show <base>:<path>`. `docs/reference-books-migration.md` is the operator
transfer table: every row a verbatim move, with its line range at the base, its
destination and an empty rename column. It lives directly under `docs/`, so it
is reviewable without becoming a book member.
`_preamble` now takes the FIRST paragraph under the H1 instead of demanding the
only one before the first H2. Most sections open with prose at the level they
already had, so the old rule could only be satisfied by promoting `###` to `##`
or inventing a sub-heading — either of which would rewrite what this migration
relocates verbatim. A source whose H1 is followed straight by a subsection
still has no introduction and is still refused, which is the property that
keeps an overview from quoting body prose as authored orientation.
`.gitattributes` pins `docs/**/*.md` to LF: chapter line ranges, byte spans and
SHA-256s are physical facts that a Windows checkout must not rewrite, and
`full-test` runs on windows-latest for every PR. The two ARCHITECTURE-derived
generated inventories are regenerated, because a chaptered section now carries
its physical provenance note.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
`docs/ARCHITECTURE.md` names the external-executor family in the refusal
register — the envelope keys, the two classes and the rule that a terminal run
whose state is `failed` or `cancelled` is still a SUCCESSFUL observation of an
unsuccessful leaf — and §11.4 records what this actually is: not "the handler
ABI window is open", but one INTERIOR family that is native among itself while
its four registered entries still publish a `str`. `docs/DEVELOPMENT.md` states
the producer/publication discipline beside the typed-producer rule it belongs
to.
The regressions go to `tests/test_builtin_refusal_results.py`, whose `_call`
harness already IS the boundary contract and which had no delegate case at all:
registry dispatch of all four verbs (handler text equals the published result,
the refusal code survives route/safety composition); the substrate-versus-agent
split against the outcome partition; the incident itself, read back through both
the typed and the text-only reader; cancel's four outcomes and a confirmed
cancel over an already settled run; answer outcomes keeping their own notes; the
quiet progress/no_progress/observation_pending window waking exactly once at the
terminal; a refused observation waking as a refusal and replaying as one in a new
process with its class, source and acknowledgement intact; the oversized fitted
envelope and the failed spill; two schema-1 reason strings replaying as one
class while the word "error" inside a success stays a success; the private cores
never touching the published-result slot; the bootstrap consumer separating a
definite unrun refusal from a post-POST unknown that must not invite a second
physical start; the hold resuming on a native leaf wake; and the acknowledgement
keyed on the published wake id, still failing closed on a truncated delivery.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Three more proxies from the 14.09 incident, one commit range (wave 3).
ensure_project_scope answered "OK: created/attached" before any durable bind
existed, its event carried no token, and the supervisor's receipt writer
reported an empty receipt id as `persisted`, so a refused bind reached only
events.jsonl. The act now rides the same rail as the other three routing
verbs, under its own synthetic `agent-steer:<token>` id: the handler returns
a typed outcome on every exit (delivered with the bound project; rejected
for a scope conflict with the rename outcome, a refused bind, a registration
failure), the tool waits through `_emit_and_wait_for_routing` and states
only the durable binding it re-reads -- `SCOPE_REJECTED` restores its scope,
`SCOPE_UNCONFIRMED` (the deferred transport is not success) keeps the
in-memory scope for journal writes and says how to read the outcome later.
`persisted` on a routing receipt is now true only when a row was written;
the promote handler keeps its admission record as the positive authority
through `annotation_status`, so promotes without an owner message behave as
before. Both SCOPE identifiers join the typed-failure register.
Receipts were latest-per-message, so a later act under the same owner
message hid an earlier act's receipt from the waiter that minted it. The
retained set is now keyed per (message, routing token) inside the existing
compaction, `chat_annotation_receipt` reads by token, the UI projection and
`_origin_already_routed` stay latest-per-message, and the picker's liveness
test ("is my token the message's latest act") is stated where it lives.
The Swarm obligation was stamped only on the admitted root, so Main paid a
plan review for work the Project root did unplanned. A promote by a root
whose obligation is still UNMET (force_plan and no plan wave recorded --
`owner_hurry.unmet_force_plan_obligation`, the same durable state
`force_plan_decision` projects) carries force_plan + source + the promoter
id; the existing `_promoted_force_plan_metadata` seam stamps the new root
and `supervisor/plan_obligation.py` releases the promoter's flag on its
live queue row before the same snapshot persist; the transfer rides the
admission record and the promoter's task details, the promote result names
the move ("if you meant to move THIS task use ensure_project_scope; work
you keep doing here is unplanned"), and the worker releases its own copy so
its finalization no longer requires a plan the new root owes. A met or
unreadable obligation moves nothing; ensure keeps it. Owner 3=A supersedes
the earlier "no recursive propagation" pins, which now state the decision.
Promote honesty: when the request's work already has a Project and the
promote landed in another one, the result says a second Project now holds
it (owner 5A: a free choice, disclosed).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
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>
A direct turn renders as an activity block without a title placeholder and is
never offered Turn into project (owner decision 14=A), so the light-model
title the direct lane coined for every fresh Main turn — one paid call per
message, a daemon thread with its own wall-clock bound and a late cost-refresh
path — has no reader left. The lane no longer spawns it; spawn_proactive_namer
and _refresh_root_cost_after_naming are deleted with the threading/contextvars
/pathlib imports only they used, the two tests that exercised them, the
terminal-writer inventory row and five test stubs that silenced the thread.
Managed promotes and headless runs keep their admission names
(admission_names, _admitted_suggested_name) and the task_named frame, so
applySuggestedName still titles managed cards. The post-task checkpoint's
same-terminal refresh stays (its own test covers it); its comments no longer
attribute it to the retired namer.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A direct conversation turn and a managed/Swarm root now share one component
with two chromes, chosen by the host's _is_direct_chat fact (census kind, the
rebuilt task_done, history rows) and never by the client. A direct block has
no Task/Working/Done chip (Cancelling…, Finalizing…, Waiting and a non-Done
outcome stay visible), no title placeholder, the typing dots as its running
indicator and the tool count, cost and duration in its collapsed header; it is
never offered Turn into project — conversion happens only through the model's
own scope tools (owner decision 14=A). A managed root keeps the full task card.
The conversion button is no longer part of the card template: syncBlockChrome
renders it from the record's facts, the transient-failure retry path calls the
same writer instead of re-authoring the button by innerHTML, and the client
gate, the retry path and the /api/state sweep in app.js read one binding fact.
The toast distinguishes an adopted Project (adopted: true) from a created one.
_pendingCardObjective and objectiveHint are deleted with their mis-assignment
window: the only consumer was a direct turn's conversion, which no longer
exists; the server names a managed root from its own origin text.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Ordinary greetings drew an empty Working/Done task card because card existence
was a class of sticky writers: ten forceCard sites, a toolCalls>0 counter, a
cardVisible latch cleared only for reusable slots, a rounds>1 history guess and
a client-side ADDRESSING_ONLY_TOOLS allowlist (an open default behind a closed
exception list) — never re-derived once set. The model-wait controller minted a
card just to host its controls, and that shell survived a resolved wait.
One predicate now decides presence (chat.js blockVisible, owner decision V1):
an always-shown kind, open attention (model wait, pending stop, host-attested
Stop), a child, a review group, a content row, or a terminal outcome other
than Done. The completion note is not content, so a zero-tool Done turn keeps
no block live, on reload or on reconnect. ensureLiveCardVisible mounts and
unmounts from that predicate at every mutation; the record and its no-reopen
wait ledger stay (G-D2), so a resolved wait removes the block and a stale
revision cannot bring it back. Successful tool calls become visible compact
rows (log_events: tool · target, then ✓ duration; failure and timeout evolve
the same row, keyed by tool_call_id), so a tool-using turn shows its block from
its first call; replay shows one summary row from the recorded per-tool counts
(noteToolMetrics), the only replay evidence of a recovered tool failure, and
block presence is the same live and on reload. A lineage-known id is minted as
its parent's nested card whichever path reaches it first (#636).
Deleted: taskUiStates and every forceCard/cardVisible/bufferedLiveUpdates
writer, forceTaskCard, forceTaskCardVisibleChange, markTaskToolCall,
markAssistantReply, markTaskComplete, scheduleTaskUiCleanup, retiredTaskIds,
reanchorVisibleTaskCard, the rounds>1 guess, ADDRESSING_ONLY_TOOLS and
addressingToolCallCount, the taskState parameter of replayTerminalPhase.
chat.js shrinks from 201,090 to 193,100 bytes, below MAX_MODULE_BYTES, so its
BYTE_DEBT row leaves the regenerated manifest. The header keeps the census
verdict beside a direct block (Thinking…) and only a managed card drives
Working…; any mounted unfinished block replaces the typing bubble. The three
replay-evidence row tests share one owner (isReplayEvidenceRow).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
The conversation activity block (WP-G) keys its chrome on whether a task was a
direct conversation turn or a managed/Swarm root. Until now only the worker's
task_done frame and the durable result carried _is_direct_chat: the supervisor's
rebuilt task_done dropped it, the authored task_summary chat row never wrote it,
history replay had no way to state it, and the /api/state census labelled a
direct turn waiting for a model after its answer as kind=managed_task (the
post-task wait owner's row copies the task dict, which already holds the flag).
One fact, every surface: the rebuilt task_done reads the frame or the durable
result (so a reaper-delivered terminal answers too); _annotate_terminal_task_truth
puts it on terminal truth from the result it already loads (zero extra reads,
history.py stays at 1599 lines by compacting two comments and one loop); the
summary row copies its own fallback value and surfaces typed_routing_action as
addressing_only, so an addressing-only turn is represented by the host's typed
action rather than a client tool-name list (owner decision 11.09); the census
uses the task row's flag for kind.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
`plan_spec.resolve_constitutional` read every non-URL string in the spec's
`affected_resources` list as a file path. On the live install that turned the
prose item «Отдельный проект «TSMC — 10-летний инвестиционный анализ»» into "a
path under the Ouroboros repository": the plan was marked constitutional and
every reviewer of a deck-shaped plan received BIBLE.md plus ARCHITECTURE.md in
full, ~470k tokens per cycle. Across the recorded history not one of the 14
constitutional verdicts came from a real file path in that list, and the model
never once spelled a target as `file://` (0 of 186 locators).
The spec now says the two things separately (owner decisions 8=A, 9=A, 16=A):
* `affected_paths` — REQUIRED — is the files the work will CHANGE, and the only
list the host resolves. `[]` is a real claim: "this work changes no files". A
path under the system repository decides whether or not the file exists yet,
because creating `ouroboros/new_module.py` IS self-modification.
* `affected_resources` is prose: systems, services, projects, people. Nothing in
it reaches the filesystem.
* An `evidence` locator is something to LOOK AT. Reading a repository file is not
changing it, so it no longer buys the pack on its own (16=A); when the work
will also change that file, the file is in `affected_paths` and decides there.
System-repo evidence reads are named in the disclosure note, so an author who
expected the constitution can see why it did not come. Existence stopped
deciding anything, so `_default_exists`/`evidence_exists` are gone with it.
A NEW spec submitted in the old mixed form is refused before any dispatch with
`PLAN_RESOURCE_FORM_REQUIRED`, quoting the field with a two-line example. The
refusal deliberately does NOT carry the `PLAN_SPEC_INVALID` token: that family
records a superseding attempt on the way out and would orphan an open wave, so
this one writes nothing and, when a wave is open, names its fingerprint and the
free `review_disposition` exit. Waves recorded under the old shape keep their
identity: `affected_paths` is never defaulted onto stored input, so their
`spec_hash` and `plan_fingerprint` are byte-identical (pinned against the three
live examples) and they still close at $0. `spec_delta` reports the appearance of
the key itself, because "stored before the field existed" and "changes no files"
are different claims.
No grace mechanism for the 31 open waves — already disclosed to the owner.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
One owner message spawns several task ids — the direct turn that received it
and the root it promoted — and each was separately convertible, which is how
one message minted two Projects. A fresh conversion closed that by binding the
origin's live siblings (#900), but an ADOPTING conversion binds only the card
that was clicked: the other cards stay task-unbound, and the client's gate is
task-keyed, so they went on offering "Turn into project" for work that already
has one (#902).
`/api/state.task_bindings` now also carries those live siblings, marked
`origin_bound`. `project_id_for_origin` stays the one owner of "which project
does this owner message's work have", including its legacy several-projects-
per-origin tie-break, and the room id comes from the binding row that names the
chosen project, so no second source can disagree. The client needs no change:
its existing truthy gate closes the button and its pointer opens the project.
The live scan both readers need is one function now
(`projects_registry.live_origin_lanes`); the sibling claim in the gateway was
carrying its own copy, and two scans that can disagree about which tasks a
message is spread across are exactly how a card gets skipped.
An origin-bound row is deliberately a GATE fact and not a re-home: nothing
durable bound the task, its chat rows are still in the chat it was started
from, and moving its live card into the project room would strand the card from
its own history. The conversion answer also always states `adopted` now — a
client that must tell "this work already had a project" from "this click
created one" cannot read that from a key's absence.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A Project root steered the Main root on 14.09; the supervisor wrote a refusal
receipt and the model was told STEER_UNCONFIRMED, so it kept retrying a steer
the host had already declined in writing. Four independent defects produced
that one sentence, and each also mis-reports other routing outcomes.
A task-authored steer carries no chat ingress id, so it went out with an empty
client_message_id: the supervisor wrote no annotation and the wait answered
`client_message_id_missing` before the handler had even run. Such a steer now
keys the synthetic `agent-steer:<token>` receipt the drained-delivery path
already mints, so refusal AND delivery are confirmable. Nothing in any chat
carries that id, so no owner message is labelled with the agent's act, and
`_relayed_owner_message` keeps relaying nothing under the prefix.
`wait_for_routing_annotation` accepted delivered/needs_manual_target/
unconfirmed but not `rejected`, which is exactly what the cancel-pending
refusal writes — so a settled refusal was polled to the deadline and returned
as `confirmation_timeout`. It is accepted as the settled outcome it is, and
UNCONFIRMED keeps its one meaning: no matching receipt exists.
The promotion receipts opened line 1 with no warning marker at all, and
PROMOTE_*/ROUTE_*/ROUTING_UNCONFIRMED/NEEDS_MANUAL_TARGET were absent from
`_EXACT_IDENTIFIER_CODES`, so a routing act that scheduled nothing counted as a
SUCCESSFUL tool call in the outcome classifier and the acceptance packet. The
family is registered beside the steer receipts, in the same recorded-but-not-
degrading bucket: the agent sees the refusal, and the host's "no" is not the
agent's execution failure.
An admitted promote named the project it ASKED for. An implicit scope is
re-resolved by the admission handler under the origin claim lock, so a sibling
card converted in the emit -> admission window moves the root somewhere the
tool never named; the wait now carries the effective destination back and the
result reports that, while an unconfirmed admission says what was requested and
that the outcome is unknown.
The differential could not have caught any of this: the four control_routing
corpus rows declared codes their plain-string producers never publish, so each
row asserted its own input. They carry no code now, which puts the producer's
own sentence in front of the one classifier.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A Project-bound root that timed out was retried under a new physical id
which copied the owner-message origin ref but carried NO durable binding,
so the retried work painted a Main card offering "Turn into project" — a
second convertible unit for one piece of work — until some later implicit
act adopted it. docs/DEVELOPMENT.md described that as deliberate; it was
the #900 implementer's size-cap deferral, and the owner accepted that a
retry inherits the old id's binding on 2026-09-14.
The reaper now binds the successor inside its own retry admission
transaction (task_reaper._run_retry_admission_transaction ->
worker_promotion.bind_retry_to_origin_project), so admission and bind are
one step:
- the claim lock moves OUTSIDE the queue lock, the order an implicit claim
already uses (project_id_for_origin's live-task tie-break takes the
queue lock under it), so nothing inverts and a concurrent conversion of
the same owner message cannot interleave;
- the bind lands only after cancellation has lost the boundary. A binding
is immutable, so a bound-but-never-admitted retry id would answer
project_id_for_task forever; a retry suppressed by a cancelled or
already-terminal root is therefore never bound;
- the predecessor's own binding answers first (the SSOT for that task's
project), then the origin-keyed lookup for a root that was never bound
itself while another task id of the same message was; the new row reuses
the predecessor's stored origin BY VALUE, so the retry joins that
message's one convertible unit instead of starting a second;
- a refused bind or an unreadable store leaves the retry unbound, admits
it anyway, and discloses project_binding_failed on the reaper's own
drive (P1) instead of holding up the retry.
supervisor/task_reaper.py sat exactly on the 1600-line hard cap, so the
call site was paid for in place: "the retry id never ran, drop its copied
inputs" existed three times in _enqueue_retry and is now one
_discard_retry_inputs owner (1600 -> 1598 lines).
Docs flip with the behaviour: docs/DEVELOPMENT.md (identity-of-the-work
section) and docs/ARCHITECTURE.md (the origin membership list and the
implicit-claim list now name the reaper's retry admission).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A root's post-task reflection received `recent_advisory_runs` belonging to
ANOTHER task on the same checkout with their identity stripped, and narrated
those failures as its own. The Pattern Register updater then got only
`reflection[:500]`: the clip cut the exculpatory clause mid-word, so the
register recorded the inverse conclusion (fail closed on REVIEW_REQUIRED,
contrary to an Advisory-enforcement install) and bumped one row twice from two
roots of a single owner request.
Commit 1b7f94973 already un-clipped the sibling argument with the rationale
that a prefix can never authorize a whole-document rewrite. This completes its
missed half; learning input stays a projection over records that already exist,
with no new taxonomy, no live-memory rewrite, no retry and no second reviewer.
- review_evidence: advisory runs are split by ROW IDENTITY into this task's own
rows (plus legacy rows with no recorded owner, which stay unknown rather than
being re-attributed) and `foreign_advisory_runs`. Repository readiness
(current_repo, obligations, debts, the exact-snapshot match) stays
repository-scoped, and `has_evidence` stays true when only foreign rows exist.
The split cannot key on the repository key, because an empty one widens the
candidate list to every advisory run on the drive. Each row now carries its
owning task, attempt, phase and complete snapshot hash (additive keys), and
the prompt renders the foreign group last, under a heading naming the owning
task ids, inside the same character budget.
- reflection: the Pattern Register call receives the WHOLE reflection and the
exact goal. `goal_exact` is stored beside the bounded `goal` display field
that logs and UI read, so neither replaces the other. The transaction is
unchanged: full old-register read, LLM call outside the knowledge lock,
re-read and exact compare under it, history before atomic replacement, one
index rebuild. A losing compare-and-swap now warns WHICH task's learning was
dropped, and the two swallowed exception sites warn with the exception text.
- knowledge tools: `patterns` joins `improvement-backlog` and `overview` as a
global-only topic (owner decision Q15). The register's writer and every
reader address the canonical drive, so a project room could otherwise mint a
second register nobody opens.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · ubuntu-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · windows-latest · claude · API key only, subscription NOT covered (push) Waiting to run
Claudexor platform gate (API keys — subscription auth NOT covered) / live · macos-latest · codex · API key only, subscription NOT covered (push) Waiting to run
Preserve supervised-task recovery, private response-failure evidence and delegated snapshot bytes. Include the reviewed Windows CI repairs.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Keep the normal Git baseline while copying original regular-file bytes into
delegated snapshots. Reject a source change before registration and preserve
existing links, patch semantics and cleanup. Correct Windows cmd quoting and
UTF-8/newline fixtures, and synchronize lifecycle tests on the actual phase.
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>
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>
Preserve free live-executor waiting before ordinary managed model recovery.
Configured-session supervisors use the same upstream-observation path as
other managed tasks; external start and patch custody guards stay in place.
Negotiate private response evidence and preserve known terminal usage when
local message normalization fails, without introducing another retry owner.
Keep unknown accounting and exact source custody explicit (P1), repair the
shared recovery boundary (P2), and reuse existing transport/result owners
(P7). Update coupled tests and architecture/development descriptions.
A refused-then-split chunk that was fully summarized carried its attempt errors
in usage and marked the run failed, so the stale error survived the advance and
Health said FAILED after a success; only a chunk that produced no content fails
now. A chronicle-only consolidation pass reports zero written blocks. The Light
maintenance prompt — its only carrier, since Light never sees the knowledge_write
schema — now asks for an authored YAML summary on new and meaningfully revised
notes and keeps an explicit standing request explicit; SYSTEM.md says the same
about corrections and holds interpretations as testable. A missing generated
index falls back to the fresh inventory so summaries stay resident. Docs state
the batch receipt, the local compactor's preserved sections, memory_mode's
canonical writes, and the read side of the Presence baseline; the Presence
end-to-end test also executes update_scratchpad and update_identity.
Prompt bytes: SYSTEM.md 24071 -> 24131.
R-01 re-integration review (astra scope + sol triad, each reproduced by
construction): a caller that passed its pre-spawn refusal could still spawn
after a peer's child died and a third caller settled it (latch set) inside
the window before the spawn lock, so the D5 contract "no spawn while
latched" was not atomic at its last decision point. The same window also let
an exited but unreaped child be overwritten by the new handle, losing its
exit fact.
`_raise_if_latched()` is factored out of `_refuse_latched_spawn()` and
called once more inside `_spawn()` under `self._lock`; `_spawn()` now returns
whenever an own handle exists (alive: join it; exited: the next settle owns
that fact). No new constant, retrier or custody kind. Two regression tests
pin both interleavings through a marker-write gate and fail on the previous
code. DEVELOPMENT's custody rule names the re-check; ARCHITECTURE's chat
module sentence now says the composer's paste/drag-drop binder lives in
chat_media.js (opus advisory).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Target drift since the PR opened (#886, #887, #889, #890, #894, #897,
#900). Two conflicts resolved: the size-ratchet BYTE_DEBT for
web/modules/chat.js is re-pinned to the merged tree's exact byte count
(201090), and the static-UI contract fixture keeps the base's entry set
with the drag-and-drop contract still pointing at chat_media.js, where
the composer paste/drop binder lives on this branch.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Owner decision 2026-09-14 (#884): the light-model duplicate judge never saw the selected subagent, rejected an intentional multi-model cross-check, caught no real duplicate in 46 days and blocked the supervisor loop on every spawn. Exact task-id fencing, caps and cost ceilings remain the admission floor.
The §6 sentence now says which owner message's receipt is left standing (a steer that
belongs to no owner message) and that a steer relaying a drained owner message keys its
receipt on that message. The drained-entry-without-client-id test starts at generation 0
so it fails on the write-counter tree instead of passing by accident. The implicit-id
comment names what the shared sanitizer normalises and what it does not.
Annotation compaction kept every `agent-steer:<token>` receipt forever (each steer
mints a new token), so a long-lived install would keep chat_annotations.jsonl
permanently above the compaction threshold and rewrite it on every append; only the
newest 64 synthetic rows survive now (routing_wait polls for at most 15 seconds).
The UI conversion refreshes an absent origin at the top of the claim, before the
adopt decision, instead of after the project was already created; the implicit
check compares the request id through the server's own sanitizer; the naming
telemetry row is written only when a project is really created; the lane mark's
snapshot persist is best-effort so a persist failure cannot poison the rollback;
the origin lookup reads the registry only when a binding names the message; the
legacy-ambiguity disclosure is written once per (store, origin, choice) per
process. The browser proof clicks the sibling unconditionally and replays the
client's stale request, asserting the adopt. Docs: a timeout retry is bound only by
a later implicit act; the one-Project claim states its conditions; the promote
paragraph matches the transaction the code now holds.
An implicit promote resolved the owner message's Project under the claim lock when
the handler started and released it, then created and bound a hundred lines later
unlocked. A sibling card converting in that window bound the origin first, and the
promoted root arrived in Main unscoped as a second convertible unit for one piece of
work. The admission body (lifecycle fence, scope on the task record, project row,
durable bind, project-thread routing, announcement) is one helper now, and for an
implicit event the handler re-resolves the origin and runs it under the same
re-entrant claim lock the UI conversion holds, so the origin read and the durable
bind are one transaction against a concurrent conversion.