Commit graph

1031 commits

Author SHA1 Message Date
Ouroboros
2e6589ce1b Merge current Ouroboros into the experimental Android branch
Preserve target chapter books, ToolResult refusals, consciousness, question semantics and meaningful comments while carrying the Android host changes forward. Release-policy updates and the disclosed combined function-count debt remain separate follow-up work.
2026-09-18 01:41:10 +03:00
Ouroboros
f94be9a06a test(chat): pin the routing receipt's action row; read the static fixture as UTF-8
Review findings (claude-opus-5 slot): `test_chat_history_paging_browser.py` still looked for the
routing receipt's Open Project button inside the note it moved out of — the assertion now names
the shared `.msg-routing-actions` row and pins its place between the note and the timestamp;
the new static check in `test_projects_v6640.py` reads `chat_activity.js` with an explicit UTF-8
encoding (the file carries a non-cp1252 glyph, and the Windows PR job has no UTF-8 mode).
Advisory: the lifecycle and answerable state lists now have one JS home in
`question_presentation.js`.
2026-09-17 20:00:23 +03:00
Ouroboros
edad94fff6 fix(chat): the activity-census pointer is complete and a narrower row never blanks it
Review finding (Claude Fable 5.1 slot, critical): `_task_activity_facts` projected only
quiz_id/state/asked_at/wait_for_answer from the owner_quiz block, so the `required_question`
pointer of the 3-second activity census arrived with empty question/options, and the browser
merged those blanks over the complete history row — the Main pointer flipped between the
question and the placeholder, and a settled pointer lost its option label.

- The census projection keeps question, options, answered_index, comment and wait_ended_at
  (the task result is already read and memoized; no new I/O).
- `project_question_pointer` emits question/options only when known, like the answer fields.
- The browser's pointer merge drops empty question/options/project_name from any re-delivery,
  so no producer can blank a painted row (pinned in chat_decision.test.js).
- `applyQuizStateFrame` observes the live frame once; the pointer repaints from the merged
  observation (advisory A1).
- The live pointer frame in `message_bus.send_quiz` is built with constant keys plus explicit
  optional assignments (`test_chat_outbound_matches_message_bus_sends` forbids `**` expansion);
  generated inventories regenerated after the `contracts.py` comment paydown.
2026-09-17 19:47:11 +03:00
Ouroboros
eda1181f46 fix(chat): the pointer row is complete, live wait facts win, one status word leads
Roast findings (codex gpt-6-astra) folded in:

- The pointer row carries the question, option labels, recorded answer and the
  wait facts from history, the live delivery and the activity census
  (`project_question_pointer`, `owner_wait_projection`; both contract mirrors),
  so Main paints it from the row alone. The IntersectionObserver hydration, the
  settled-source cache and the Retry state are gone: freshness is the ordinary
  history reconciliation plus the `quiz_state` frame, and task detail is read
  only to open the original form.
- Lifecycle observation precedence: once a live frame closed a wait, an older
  history row or a detail read begun before it cannot reopen «Waiting»; an
  unavailable row keeps what is known; a settled question never reopens. The
  production timeout frame (`wait_for_answer:false`, no `wait_ended_at`) is the
  shape the tests use.
- Parity: history attaches the task's wait record to the Project room's quiz rows
  (a wait the owner resumed by ordinary input leaves no frame behind), the quiz
  card reads those facts, and the parity fixture now pins the rows Python emits
  against what the browser reads from them.
- Wording leads with one word — «Waiting for your answer» / «Unanswered · …» /
  «You answered» / «Replaced by a newer question» / «Status unavailable» — with
  three action labels; status and source lines use meta ink (DESIGN: the owner
  reads them to act). The wait-ended line names the default path the task took
  and that silence was not consent; a late answer's toast says where it went.
- Previews bound the option and the comment separately and never cut for less
  than the marker costs.
- Module map row for `question_presentation.js`; DEVELOPMENT 11 points at the
  ARCHITECTURE data flow instead of restating it; a stale comment in
  `owner_quiz.py`; `contracts.py` pays its 1600-line gate down by compacting
  five comment blocks.
2026-09-17 19:30:17 +03:00
Ouroboros
1fdfc722b5 fix(chat): question pointers say whether an answer is wanted and give their button room
The Main-chat pointer for a required Project question read «Question answered in
<Project>» with its `View question` button jammed against the timestamp, and the
lifecycle words did not tell the owner whether a question was waiting for him.

- One shared action-row composition, `ui_helpers.createSystemMessageActions`, now
  owns space above and below the buttons, wrapping and focus-ring clearance for
  question pointers, Project lifecycle rows and routing receipts; a button never
  sits in a nowrap text line again.
- One lifecycle vocabulary in the pure `web/modules/question_presentation.js`,
  shared by the pointer and the quiz-card header, with the Python fallback in
  `project_dialogue.project_question_pointer` pinned by a shared parity fixture.
- The pointer shows the question first, then the status, the recorded option and
  comment (never a locally invented answer), and the Project as its source.
- The answer POST settles the card only on a valid recorded confirmation
  (`ok`, `state`, a valid index or a non-empty comment); a malformed 2xx never
  substitutes the local draft (the independent audit's fabricated-answer defect).

Docs replace the touched descriptions in DESIGN §5, ARCHITECTURE 03 and
DEVELOPMENT 11. Version carriers are untouched: a contributor PR into `ouroboros`
leaves the release version to integration.
2026-09-17 19:30:17 +03:00
Ouroboros
b1565ec934 Merge managed/ouroboros (PR #1007, tool rows) into claude/custody-reviewer-20260916
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 02:57:29 +03:00
Ouroboros
8d605f18a8 fix(chat): a replayed card row keeps its history identity; docs state the placement exceptions
A card row replayed from history now merges through the historical timeline
seam with its history id and position, so it sorts by its source position and
leaves the card when its page is released, like every other replayed timeline
item. The custody split derives the row id from the task's canonical answer
identity when the answer reaches the seam unregistered, and keeps the joined
host notice when no task can key it. The late acceptance row's head states what
the host holds (no settled verdict) and names a reviewer whose outcome is still
unknown. A placed row raises the unread badge like a standalone row did.
DESIGN §4 and ARCHITECTURE 03 name the rows that stay ordinary by design.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 02:22:10 +03:00
Ouroboros
88aa2e4066 Give the running evidence row primary ink, not the accent
The accent reads as an alarm beside the error rows; a row that is what the
turn is doing right now is the primary thing in its card.
2026-09-17 01:55:01 +03:00
Ouroboros
a3bf557a88 Keep the reflowed comments saying what they said
The earlier reflow to hold the 1500-line band dropped two facts: a failed call
still counts in the evidence total, and the completed-event normalization takes
its label, phase and terminal truth from the canonical projector. Both are back,
paid for by the cost-checkpoint note and the child branch, which say the same in
fewer lines.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:44:58 +03:00
Ouroboros
45ddd7c5d7 Tint the evidence row while it runs and once it warns
The folded row's documented phases were invisible: only `error` had a colour,
so a turn mid-burst and a turn that lost a call looked like a settled one. The
row keeps its counts-only label and says the phase in ink, with the same
colour-only shape the error rule uses.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:39:59 +03:00
Ouroboros
6fc5126fc1 Let a child card read the turn's voice fact
A child's own progress frames carry the same typed `narration` fact a root's
do, but the subagent branch promoted every one of them, so a checkpoint or
fallback note inside a child's turn took over the child card's collapsed
activity line. Its non-terminal frames now follow the fact (absent stays
legacy narration), while the host's lifecycle, result and error frames keep
leading, and the collapsed line takes a child's activity only from a frame
that speaks in the turn's own voice. Two comment blocks around the branch say
the same in fewer lines, so the module stays at its 1500-line band bound.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:39:32 +03:00
Ouroboros
a230faec99 Drop the dead tool-fold prefix from the in-place key list
The folded evidence row never reaches the generic timeline upsert — the owning
frame is skipped there and `upsertToolFoldRow` passes `inPlaceByKey` itself —
so the `tools|` entry in the prefix list decided nothing.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:39:21 +03:00
Ouroboros
ff64a45c94 fix(custody): a task's own reviewer is not its delegation; host facts are card rows
A review slot's run registered under the reviewed task's id was treated as
that task's delegation: the loop-exit audit listed it as open delegated
execution (Done with warnings plus a standalone System bubble) and the
periodic orphan sweep cancelled the live reviewer once the task's durable
result read completed (issue #1006).

Custody kinds: `RunCustody.review_owned` (the durable `source` under the
review substrate, consolidated from the private recovery predicate) is now
read by the orphan/kill reconcile (a review-owned row is cancelled only
behind an owner cancellation, otherwise left live with a typed reason), the
terminal custody audit, the execution-evidence counters, the nanny hold, the
crash-recovery candidate lists and the pending-invocation recovery (a review
invocation is retained, never re-posted). SETTLED rows carry `source` and
`category`. Physical custody keeps seeing every run. The consumer matrix in
tests/test_custody_owner_kinds.py is the surface a new reader joins.

Card rows: a host fact about a task is a row of that task's card. The
producer stamps `card_row` (timeline | reviews) and `card_row_id` on the chat
row (persisted by log_chat, replayed by history, mirrored in ChatOutbound).
The custody audit becomes its own typed row (`custody_notice`, its own owed
delivery id; the outbox rebuild no longer appends a host line into the
assistant answer). The late acceptance settlement stamps a host-composed
`late_settlement` note on the panel projection and its row is placed in the
card's Reviews group, which prints the note verbatim. The browser attaches a
stamped row to the task's card record as one timeline item, live and on
replay, and falls back to the standalone System row only when no card record
exists. Single-body transports keep the joined host notice text.

Docs: ARCHITECTURE 01/03/06/11, DESIGN §4/§5, DEVELOPMENT 06/11 state both
rules; generated inventories regenerated.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:35:18 +03:00
Ouroboros
892fd8886d Merge the host's tool totals field-wise
The supervisor states a turn's totals more than once — the metrics event,
the terminal, a replayed summary — and each fact carries its own subset.
Replacing the whole snapshot let a later partial fact (a bare `tool_calls`)
erase the routing count that classified an addressing-only turn as a
receipt, and erase a known error count with it. Each field now keeps its
last known value until a fact actually states it, and `tool_call_counts`
counts as stated only when it is a non-empty object, so an empty map keeps
the live names instead of emptying the row behind Expand.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 01:33:43 +03:00
Ouroboros
e48d6adf0e Keep log_events.js inside its size band after the merge
The two folded-evidence and voice comments say the same in fewer lines, so the
module stays at the 1500-line band bound the manifest records.
2026-09-17 00:58:45 +03:00
Ouroboros
8f28857fdc Merge WP-B: the typed narration fact keeps host notes out of the card title
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 00:56:34 +03:00
Ouroboros
228acaada1 fix: fold a turn's successful tool calls into one evidence row
A turn that reads five files drew five rows, so the narration around them
scrolled out of the block. Routine execution is now ONE stationary row per
block: it counts the calls, names the tools behind Expand, and keeps the place
and the timestamp of the first frame it counted. Failures and timeouts keep
their own row where they happened and still count in the total.

The accumulator is a per-invocation state map on the record rather than a pair
of counters, so duplicate, reordered and concurrent frames all settle on the
same reading: a status never regresses, an error is counted once, and a late
start cannot reopen a finished call. One builder produces the row for both the
live frames and the host's metrics, and the meta counts follow the same
reading, so the header cannot disagree with the row while a turn runs.

The host's totals replace the derived ones only where the host stated them. An
absent field stays absent instead of reading as zero, so a terminal that
carries `tool_calls` alone can no longer turn a block that only addressed work
into content. `noteToolMetrics` drops its per-call-row guard and always upserts
the same key; the keyed upsert makes a second row impossible.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 00:43:41 +03:00
Ouroboros
e599a257b1 Keep the card title and the activity line for the turn's own voice
Owner decision 16.09 (Q1 = A): host-authored notes stay visible timeline rows
but never become the card title or the collapsed activity line. Only the model's
narration does.

The progress projection reads the typed `narration` fact instead of promoting
every progress frame: a host note keeps its row, its phase and its markdown, and
loses only `promote`/`human`, which are exactly what feed the title and the
collapsed line. A frame without the key is a legacy frame and is promoted as
before, so older workers, supervisor-authored notes and stored rows are
unaffected. The progress reconstruction forwards the key from the history row,
so the replay of a turn behaves like the live turn did.

A turn whose progress was host notes only therefore keeps its coined name (or
the running placeholder) and shows an empty activity line.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 00:39:24 +03:00
Ouroboros
002013432e Name the voice of every progress note on the frame itself
A progress frame carries the model's own round narration and the host's notes
about the turn (checkpoints, fallback, plan, acceptance, nudge, transport,
density) under one type, so the only way a reader could tell them apart was to
match the note's wording, which BIBLE P5 forbids.

The worker now stamps the fact instead. `_emit_progress` takes `narration` and
writes `progress_meta.narration` on EVERY frame it emits, so absence means "an
older worker or a row written before the fact existed" rather than "a host
note". `_emit_round_progress` is the single producer that passes True, for both
of its emissions; the loop-level notifier and the whole ToolContext ABI
(`emit_progress_fn`, one positional argument) keep the default.

The key needs no transport work: the delivery seam already spreads progress_meta
onto the top level of the live frame and of the stored progress row. It joins
the ChatOutbound contract in both mirrors and the progress-meta whitelist, so a
reload replays the same voice the live frame carried. `cancelable`'s comment is
reflowed, without changing a word, to keep contracts.py at its 1600-line cap.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-17 00:39:17 +03:00
Ouroboros
33f3bd006c Merge remote-tracking branch 'managed/ouroboros' into claude/consciousness-redesign-20260916 2026-09-16 16:41:17 +03:00
Ouroboros
2570ee1841 Keep the effort option table in the shape the static mirror guard reads
The full battery on 3ac880fd9 failed one test: the JS effort list is a
hand-maintained mirror of config.EFFORT_SCALE and its guard reads the
`{ value: '<tier>' }` entries inside the EFFORT_OPTIONS literal; the folded
pair table hid them. The literal is back, two entries per line, so
settings_ui.js stays under the 1000-line band.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 16:40:38 +03:00
Ouroboros
3ac880fd97 Consciousness effort inherits the Task / Chat effort when unset; pin the retired card helper
Owner decision (16.09, 1=A): an empty OUROBOROS_EFFORT_CONSCIOUSNESS slot now
means the Task / Chat effort, the same way an empty model slot means Main — a
wake-up is an ordinary Main turn and shares its request shape. A set value is
honored; an invalid one is treated as empty. The settings default becomes empty,
the Consciousness segmented field gains a "Same as Task / Chat" option, and the
configuration doc row says so.

Semantic merge fix for PR #970: its static UI contract pinned blockHasWork on
the shouldAlwaysShowTaskCard helper that this branch retired with the
bg-consciousness card kind; the fixture now pins the retired form (the
quick-test failure on 505f6f4ae).

The effort option table folds onto two lines so settings_ui.js stays under the
1000-line ratchet band.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 16:34:12 +03:00
Ouroboros
505f6f4ae3 Merge managed/ouroboros (PRs #970, #983) into the consciousness redesign
Conflicts: supervisor/log_addressing.py and supervisor/worker_chat_lane.py (the turn
event queue carries both the initiator label and the lazy-naming callback),
web/modules/chat.js and model_wait.js (the target's chrome-follows-the-work model
wins; no lane placeholder survives), tests/test_log_forwarding.py (both blocks),
docs/architecture/03 (their chrome paragraph plus the consciousness wording),
docs/DOMAIN_MAP.md and the v7next inventories (regenerated). The merged get_tools
sits at the 300-line ratchet cap (one entry joined onto one line).

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 16:06:11 +03:00
Ouroboros
26733255fb UI smoke follow-up: anchor a card crossing the viewport top on what follows it; re-pin two rows
The full host UI smoke lane on the merged head (workflow_dispatch; never run on
pull requests) showed three failures caused by #970. One was a real regression:
the viewport-preservation anchor picks a boundary inside a live card that
crosses the top of the viewport, and a wait-only block now carries no title
placeholder and no conversion button, so nothing inside it was anchorable and
the capture fell back to the card's own top — a wait update that shrank the
block moved the reader's messages by 40 px. Such a card now anchors on the first
message that follows it, which is what the reader is looking at (unit pin in
render_batch.test.js; the model-wait browser test passes again).

The other two were pins of text the sprint changed on purpose: the `Reason:`
label in front of a cause sentence and the answer excerpt in the Main
completion row. Both tests now pin the shipped shape. The remaining 34 lane
failures predate #970 and are filed as their own issue.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 15:52:08 +03:00
Ouroboros
56198be6bb Consciousness: review round 5 fixes (execution verbs withheld at Observe, refusal debounce, closed-bound reconciliation)
- Observe does without skill_exec and run_ci_tests (an enabled skill's script; a branch
  push plus a workflow dispatch) — the two remaining built-in execution verbs.
- A refused wake launch debounces the next event like a skip does, so the backoff it
  armed is never undone on the next supervisor pass.
- The direct lane names the card's chat in the closed-bound frame; reconciling a replayed
  row into an existing card projects the closed bound too (a missed timeout frame).
- Docs: PERSISTENCE drops the retired observation fold; architecture/06 names the two
  round-4 gates the mode cap binds at; DESIGN describes the question pointer's states.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 12:22:17 +03:00
Ouroboros
4408d25b40 Merge managed/ouroboros (PR #964, promote refusals) into the consciousness redesign
Conflicts: ouroboros/tools/control_routing.py (both kept: the presence note inside
its branch, the consciousness origin stamp after it), docs/architecture/03, 06, 12
(their new sentences kept, the consciousness wording re-applied), and the generated
v7next inventories (regenerated).

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 11:33:26 +03:00
Ouroboros
4ddb7aa112 Consciousness: review round 3 fixes (review spend in the allowance, late-answer transport, timed-out wait card)
- A deep review a consciousness tree starts keeps the tree's ledger category, so the
  rolling allowance discovers its root and counts its spend (its rows said
  deep_self_review and the tree was invisible to the allowance).
- A late quiz answer relayed by a transport skill is forwarded with that skill as its
  source (answer_decision takes an additive `source`; the web card stays "web").
- A bounded owner wait that times out stops saying "waiting": the quiz block drops
  wait_for_answer (wait_ended_at for audit), the live card gets an additive
  quiz_state wait_for_answer=false, and the card stays open and answerable.
- Wake message: the newest unanswered cards first, bounded to four, then settled tasks
  newest first; one placeholder pass (a title containing "{daily_usd}" is a fact);
  "at least" before the spend when unmetered rows sit in the window.
- Only a consciousness refusal pauses an evolution campaign; any other refusal retries
  on the next pass without recording a cycle.
- A root consciousness started reads the runtime mode it actually runs in (the per-task
  light cap) in its Runtime block; the wake itself keeps Main's block byte-identical.
- A wake whose thread cannot start unregisters itself; /bg start after a long off
  period never announces a wake in the past; the nanny's "chose not to delegate"
  nudge respects a withheld delegate_start.
- docs/architecture/06 names the gates the mode cap binds at; a stale docstring
  mention of the retired observation inbox is gone.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 11:24:07 +03:00
Ouroboros
75ccb7b50c Consciousness: review round 2 fixes (owner stop provenance, deep-review origin, refused cycle pauses, honest allowance)
- A stop remembers who placed it and the key never outlives the stop: an agent's
  toggle_evolution(False) on top of the owner's stop keeps the owner's; /evolve off and
  panic drop any agent source; every clear drops the key. Only an agent-placed stop is
  undoable by the agent (В12).
- request_deep_self_review starts a root: Observe does without it, and the request
  carries the caller's consciousness origin to the ONE admission door and the ledger.
- The globalized view of a project task keeps the consciousness origin, so a campaign a
  Full tree promotes stays inside the consciousness limits.
- An evolution cycle the admission door refuses pauses the campaign once with an owner
  line (like the budget-reserve breaker) instead of minting a transaction and bumping
  the cycle on every supervisor pass.
- The allowance is read before the queue lock (the ledger flock never stalls queue
  readers); a refused wake launch keeps its event reason; a silent gate refusal inside
  admission is typed as the gate; the wake message lists cards first and settled tasks
  newest first.
- UI: the allowance line never wears a $0.00 receipt for an absent number and prints
  "≥" for an unmetered window and "ledger integrity degraded" when flagged.
- docs/architecture/06: a wake is an ordinary direct turn (no background owner, locked
  memory, pause/stop event of its own; the interactive transport class applies).
- Size-ratchet manifest regenerated.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 10:45:51 +03:00
Ouroboros
c06a580ca0 Merge managed/ouroboros (PR #964 promote refusals) into claude/task-card-chrome-20260916
Three conflicts resolved by keeping both sides: the chrome tests and the
refused-promote test in web/tests/chat_activity_block.test.js; the admission-notice
sentence beside the cause-table verdict sentence and the refused-addressing sentence
beside the work-keyed chrome sentence in docs/architecture/03; inventories regenerated.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 10:38:44 +03:00
Ouroboros
56ff4eede9 Consciousness: review round 1 fixes (alarm re-arm exclusions, graceful wake ceiling, own cards, honest status)
- The alarm no longer re-arms on the owner's own direct turn ending (B13) nor on a
  project digest of a tree consciousness started; notify keeps the boot floor after
  a restart (max(last wake, boot) + floor).
- A wake carries what is left of its allowance as the tree's GRACEFUL ceiling
  (`root_cost_ceiling_usd`, now honored for the root itself by the in-task stop)
  instead of narrowing the ledger fence: one Main attempt reserves ~$8 up front, and
  the narrowed fence refused every wake of a nearly spent day before its first call,
  posting a budget error into Main at each heartbeat (stand: 15 such wakes). A
  remainder at or below the planning margin is skipped as allowance_exhausted.
- A wake the lane could not admit backs off like a failed wake; a closed budget door
  is retried quietly at the interval, a transient door at the floor.
- The wake message lists unanswered cards of any task, the previous wake's own
  included (an expired_terminal card still takes a late answer).
- Only an owner's stop is sticky against toggle_evolution: a stop the agent placed
  remembers its source (`evolution_stop_source`) and stays undoable by the agent.
- The status snapshot carries `unknown_unmetered`/`integrity_degraded`; the allowance
  line says "at least $X" and "ledger integrity degraded" when they apply.
- Docs: a Presence cycle a wake starts is outside the allowance; benchmark profiles
  drop the retired OUROBOROS_BG_MAX_ROUNDS key.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 10:09:18 +03:00
Ouroboros
3b36200880 Retire the bg-consciousness special cases from the UI and the gateway
A wake-up is an ordinary direct turn with its own task id, its own durable
result and its own card, so the pseudo task id "bg-consciousness" no longer
names anything live. Every special case built around it goes:

* chat_activity.js loses isBackgroundTaskId/shouldAlwaysShowTaskCard (the
  block appears under the same predicate as any other turn) and the
  "Background thinking" meta hardcode; the P1 origin label is the only one
  left. task_control_menu.js drops the id from REUSABLE_TASK_IDS, so a wake
  offers Stop and is scanned like any other unconfirmed card.
* model_wait.js loses syncBackground, the background owner, the paused phase
  and the "this wakeup cycle" wording; a wake's model wait is a direct turn's
  model wait. The consciousness role label stays (the model slot is honored).
* logs.js and log_events.js file and label a wake by `initiator` only; the
  end-of-cycle `consciousness_state` projector and the "thought"/"background"
  vocabulary are gone, as is the dead `source === 'consciousness'` own-loop
  branch.
* gateway/history.py no longer stamps the loop's last progress row "done" and
  no longer appends its live model-wait row. The retired loop wrote no
  task_result, so its legacy rows replay as any other row whose result is
  gone: the client's durable task-detail read settles the card as "Outcome
  unavailable", never as Done. The dead get_background_model_wait reader and
  the `background` parameter go with it.

The remaining stale mentions (a compacted context section, a context-health
error type, docstrings and comments) are corrected in place.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 06:13:31 +03:00
Ouroboros
8212eebed3 Replace the consciousness loop with a supervisor-ticked alarm clock and a wake-up message
A consciousness wake-up is now an ordinary Main direct turn nobody typed
(owner decisions В13/В15): the same system prompt, memory, tools and loop as
an owner's message, started through handle_wake_direct with the wake's
origin/authority envelope and a rendered user message. ouroboros/consciousness.py
is rewritten as the alarm clock only (1533 -> 289 lines): tick(now) runs from
the supervisor loop beside publish_direct_roots — disabled -> live wake ->
live owner turn -> not due -> rolling-24h allowance (allowance_unknown is a
disclosed skip, never a silent block) -> owner chat bound -> launch; the next
wake is last finish + the model's set_next_wakeup interval (or
WAKE_DEFAULT_SEC), clamped into the owner's bounds, doubling on a runner
failure; notify(reason) pulls the next wake to max(now, last wake + min)
(arithmetic debounce) from a finished root task (any outcome, never a
consciousness-origin one), a project digest and the orphan-heal sweep; an
owner message no longer wakes it. Liveness is the DirectActivityRegistry
alone; /bg stop and toggle_consciousness(stop) arm a graceful stop of a live
wake off-thread; the legacy observation inbox is moved once to archive/
without being read. The health WARNING/CRITICAL trigger is not implemented
(no producer exists) and says so in the docstring.

prompts/CONSCIOUSNESS.md is now the wake's USER message template, rendered by
the new ouroboros/consciousness_wake.py (events since the last wake from
task_results, open owner cards and owner-message count, truncated with an
explicit "(+N more; see recent_tasks)" line; wake_task_metadata builds the
envelope). set_next_wakeup becomes an ordinary registry tool in
tools/control.py.

Gone with the loop: BACKGROUND_DELEGATION_ROLE and its consumers
(owner_delivery deferred frames, the _escalate refusal), the bg model-wait
twins and get_background_model_wait plumbing, BG_CONTEXT_*/BG_OBSERVATIONS
budget constants and the inbox hot-store tripwire, the CONSCIOUSNESS.md
whitelist drift check, the startup inbox fold, the consciousness pause/resume
around owner turns and both "Message from my human" injections. The
OUROBOROS_BG_WAKEUP_MIN/MAX keys leave the restart-required set (read at each
alarm decision); docs/architecture/07 and the Settings note say so.
server._describe_bg_consciousness_state projects honest statuses (disabled /
stopped / thinking / sleeping until HH:MM / waiting_for_first_conversation /
allowance_exhausted / allowance_unknown / wake_rejected / wake_failed);
Activity and Evolution render the new snapshot; api_types documents it.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 06:03:21 +03:00
Ouroboros
6a78a8fe0b Accept a late owner quiz answer and deliver it as an owner message
A question card no longer dies with its author (owner decision В17a=A, which
explicitly retired 30=A). Expiry stays structural — the task-done seam still
flips an unanswered card to expired_terminal — but the answer ingress now
accepts an answer on that expired block, records it with
answered_after_terminal for audit, and keeps first-answer-wins exactly as it
was.

Because a finished task has no mailbox anyone will drain, the recorded answer
is delivered into the card's own chat as the owner's OWN message through the
named ingress accept_local_message: its canonical chat row is the acceptance
receipt and its client_message_id (quiz_late_answer:<task>:<quiz>) is the
idempotency key, so a retry after a failed delivery re-enters the same
delivery instead of enqueueing twice. The message text is the existing full
_quiz_answer_frame, untrimmed; provenance rides its own task_metadata field
so the real transport's client_surface is never substituted; and the owner
gets the same WS echo bubble their own typing produces.

The 2xx now says answered_after_terminal and forwarded, with a reason_code
when the card's chat is synthetic A2A traffic or the hidden partition — a
destination with no owner turn to start. 409 is left for a card that was
already answered, so the ingress stops fabricating an expiry on a bare
refusal. record_asked stores the card's chat_id (and a waiting asker's
max_wait_minutes) so the delivery addresses where the card was shown, with
address_task_event as the fallback for older blocks.

On the web the card stays answerable after its task finishes: the option
buttons and the free-answer field survive expired_terminal (only answered or
superseded turn the card into a record), the status reads "Task finished — you
can still answer", the project pointer reads "Answer still possible" and keeps
refreshing, and a bodyless 409 no longer invents an expiry the card obeys.
Telegram reports the same two outcomes honestly. Without the web half the
feature would have been reachable from Telegram alone.

Disclosed properties: in a project room with exactly one live steerable root
task the ordinary routing hands the late answer to THAT task's mailbox; and a
card evicted by the 16-block cap answers 404, because it is genuinely no
longer known.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 05:19:22 +03:00
Ouroboros
cf424b48fd Review round 3 nits: honest wake counts, answered slot ids, wording
The wake head now counts the slots that answered before the release (their
verdicts live in the collected panel) so its "N of M" is on the same basis as
the quorum; the roster records answered slot IDS rather than a count, so a
re-released wave that replays a settled slot never counts it twice. The
accepted-on-earlier-revision sentence says the answer "changed" (a keep with a
new review subject moves the paid identity without rewriting the bytes). Two
record-only "valid quorum" remnants reworded; a test docstring moved above its
import; a redundant sleep dropped.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 05:05:35 +03:00
Ouroboros
08a97e0abc Carry a turn's origin label (initiator) from its frames to replay and the chat UI
Background Consciousness redesign, P1 (label carrier). A self-initiated Main
turn — a consciousness wake-up, and later the roots it starts — is marked by
ONE fact, `metadata.initiator = "consciousness"`, and that fact now survives
every hop an owner-visible message takes, so the block's meta line and the
final bubble are labelled live and after a reload alike:

- Producers: `subagent_messages.initiator_meta()` reads the label from a task
  record or its metadata; `agent._subagent_progress_meta` merges it into every
  progress/heartbeat frame, the terminal `send_message` frame carries it next
  to the child lineage, and `TurnEventQueue` stamps it (optional `initiator`)
  on the turn's own tool/log events beside `_is_direct_chat`.
- Persistence: `log_chat` copies it onto the chat.jsonl row like
  `task_terminal_status`; the authored `task_summary` row and the persisted
  task result carry it too.
- Replay: `history._PROGRESS_META_FIELDS`, `_copy_task_summary_metadata`, the
  terminal-truth overlay (from the stored result metadata) and the chat-row
  copy return it on each row (history.py stays at the 1600-line gate:
  the four additions are folded into existing lines).
- Envelope: additive `ChatOutbound.initiator` (+ the JSDoc mirror), pinned by
  the contract parity test; one row in the frozen-contracts chapter.
- Web: `CARD_META_KEYS` carries it into the live projections
  (`summarizeChatLiveEvent`/`taskTerminalSummary` stamp it), the block meta
  line shows "Consciousness", the assistant bubble is signed
  "Ouroboros · Consciousness" through the existing sender line, Logs files the
  rows under Consciousness (`categorizeLogEvent`) and labels the task group;
  the legacy `bg-consciousness` branches are untouched (P5 removes them).

Tests: tests/test_consciousness_initiator_label.py (frames, chat row, summary
row, result metadata, history replay), web/tests/consciousness_label.test.js
(sender line, categories, projections, live meta line/bubble, tool-only wake,
owner turn unlabelled, reload), the Logs group label in
web/tests/dashboard_read_states.test.js.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 04:28:54 +03:00
Anton Razzhigaev
336227bfed Review round 1: route_to_project codes in the cause table, typed reason with model words as detail, host cause on the owner-routing receipt, honest fallbacks and narrowed docs
- `route_to_project` keeps its typed abstention code (`target_unspecified` /
  `invalid_project_id` / `target_not_found`) as the receipt `reason` and carries
  the model's own words as `detail`; the three codes and `project_unavailable`
  join the host cause table, so an empty-options abstention reads
  "Not started: no destination was chosen" instead of a raw code.
- `_record_routing_receipt` (owner routing) reads the same host table: the
  `project_unavailable` refusal carries `reason` and `cause` on the durable row,
  the live ack and the fallback broadcast.
- A refusal receipt with neither options nor a cause sentence (rows written
  before the sentence existed) reads "Not routed", never "Choose a target".
- An untitled host-issued act is named by its request's first words, cut at a
  word boundary with an ellipsis.
- Docs narrowed to what the code produces: `detail` on the producers that hold a
  fact beyond the code; `(reason: detail)` on the promote/route identifiers only;
  `cause` on the frame and replay, `cause` beside `reason` only on the 409 body.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 04:18:23 +03:00
Ouroboros
576d711676 Acceptance review is advice to Ouroboros, not a byte gate (WP-7)
Owner decision D4=A (2026-09-16): reviewer verdicts are for Ouroboros, and a
review that only ever reached Main by Main not moving on was worthless to it.
Four changes, one new leaf (ouroboros/acceptance_settlement.py):

1. The released wave wakes Main at its own quorum (min_successful_slots) and
   again when the last slot settles; the mailbox row carries each reviewer's
   own parsed verdict and note instead of "reply keep to collect". The
   settlement thread still never aggregates (review_custody roster gains
   `verdicts` / `quorum_announced`; two module-private helpers).
2. A final answer delivered while the panel this turn released is still
   running neither buys a second panel nor is refused one
   (`_deliver_under_running_panel`, inserted after `_prior_acceptance_run`).
   Waiting is the default; under advisory enforcement Main may answer
   `"pending_review":"finish"` on its delivery control (classified beside
   `acceptance_subject`, recorded on every control answer, offered in the
   prompt only where the host can honour it — blocking always waits, Cyber Pro
   keeps its own rule). A panel that settled PASS on the earlier revision
   accepts the task on the reviewers' word (`previous_revision_accepted`,
   owner fork 1=B) and the row says so; a FAIL or DEGRADED on the earlier
   revision is not a verdict on the rewrite and hands the delivery to the
   ordinary path (a new panel while the cap allows, otherwise the typed
   refusal), so a rejected old draft never paints the new one red.
3. A panel that settles after its task ended is collected at $0, republished
   on the task's review projection and announced once in the task's room
   (`acceptance_late_settlement`, deduped by delivery id; owner fork 2=A).
4. The DEGRADED progress line no longer claims a quorum failure.

Renderers: an accepted decision whose reason has a table sentence now states
it (both twins); one phrase; the completeness gate scans the new leaf; one
parity case. Tests N1-N6 pin the incident shape (4525349b) end to end, the
quorum wake, the late supplement and its idempotence, the advisory finish,
blocking/Cyber Pro rows, and the untouched finished card. Docs: architecture
01/06, development 06, DESIGN, inventories.

Sizes: loop_acceptance_review.py 1586 -> 1572, review_custody.py 1505 -> 1553,
loop_delivery.py 1300 -> 1317 (band), log_events.js 1492 -> 1496,
outcomes.py untouched.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 04:03:44 +03:00
Anton Razzhigaev
2f8aa7d4e2 Promote refusals: cause to the model, receipt-only for live turns, one typed System row for host-issued acts
A supervisor refusal of a chat-issued promote used to reach the owner as a
standalone Ouroboros bubble (WORKSPACE_UNUSABLE ...), reach the model without
its cause, and label the owner's message "Choose a target" with no options.

- Every workspace refusal returns `detail` (cause + repair) composed by
  `workspace_admission.workspace_repair_hint` from the typed source of the
  refused folder; `_persist_promote_rejection` is the single durable writer.
  `_fail_promoted_task_loudly` and `_explicit_workspace_remedy` are gone.
- Placement follows who can narrate: a tool-issued act gets its receipt, the
  failed-call error row and `detail`; a host-issued act (skill card, Swarm,
  picker click, stamped `host_initiated`) gets ONE typed System row
  (`task_not_started` / `task_start_unconfirmed`) in the chat the owner wrote
  in, from the one publication boundary `_handle_promote_chat_to_task` wraps
  around every promote outcome. The skill-repair untyped bubble is removed;
  steer cancel-pending notices obey the same owner-labelled rule.
- The host owns the owner-facing sentence: `project_dialogue.routing_refusal_cause`
  (action + status + reason, e.g. "Not started: the working folder can't be
  used") rides the annotation, the live `message_annotation` frame, history
  replay, `MessageAnnotationOutbound`/`DecisionResponse` and the picker's 409
  body; the browser renders `cause` verbatim and keeps no client table.
- Admission-notice rows stay in the chat they were sent to on replay
  (`room_membership` ignores the never-started task's project binding); the
  "Project · Started" row is announced only after the task is really queued.
- `workspace_root` naming the Ouroboros repository itself maps to the existing
  `workspace="none"` sentinel at the promote tool with a disclosure; subfolders,
  the data drive and every other caller keep the typed refusal.
- Docs (DESIGN, architecture 01/03/04/05/06/12, DEVELOPMENT naming rule),
  generated inventories, python and web tests.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 03:56:03 +03:00
Ouroboros
c98731ff14 Add consciousness autonomy, allowance and concurrency settings; retire OUROBOROS_BG_MAX_ROUNDS
Settings scaffolding for the Background Consciousness redesign: the three keys that
will govern a wake-up, and honest new bounds for the two that already exist. No
runtime behaviour changes yet — consciousness.py still reads the wake-up bounds at
__init__ and adopts these readers in a later phase.

- OUROBOROS_CONSCIOUSNESS_AUTONOMY (observe|act|full, default act): what a wake may do.
- OUROBOROS_CONSCIOUSNESS_DAILY_USD (20.0): rolling-24h spend ceiling for consciousness,
  its wakes plus the tasks they start; 0 means it may not spend.
- OUROBOROS_CONSCIOUSNESS_MAX_TASKS (2): concurrently running consciousness-started
  tasks; 0 means it never starts tasks.
- OUROBOROS_BG_WAKEUP_MIN 30 -> 900 and OUROBOROS_BG_WAKEUP_MAX 7200 -> 14400, same
  semantics: the lower/upper bound of the interval the model picks via set_next_wakeup.
  WAKE_DEFAULT_SEC = 3300 is the SSOT interval used when it picks none — 55 minutes,
  just under the default 1h prompt-cache TTL so the shared prefix stays warm.
- OUROBOROS_BG_MAX_ROUNDS is retired: a wake is an ordinary Main turn, bounded by
  OUROBOROS_MAX_ROUNDS and the per-task cost cap, so a consciousness-specific round cap
  has no reader. It leaves the restart-required set with the same edit, so a stored
  ghost drops at settings load and the boot notice names it.

Readers live in runtime_limits.py with the other clamped getters and are re-exported
through config.py. The three new keys are deliberately NOT restart-required: they are
read at decision time, so a save applies without a restart.

Settings UI: the Background Cognition section gains an autonomy segmented control, a
float Daily Allowance field (collected as text, like the evolution budget reserve, so
20.5 is not truncated to 20) and a Max Concurrent Tasks field; the wake-up inputs are
relabelled as the bounds of the model-chosen interval and the max-rounds input is gone.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 03:55:06 +03:00
Ouroboros
e74e44017f Review round 1 fix batch: title cascade, tier words, one repair per episode
Four review lanes (grok, astra, opus-5, astra scope) on 73ef007f2; the accepted
findings, applied together:

- The general empty-title rule lost the cascade to the collapsed-title clamp
  and its reserved line (equal specificity, later position), so an attention-
  only block still reserved a blank title row. A collapsed-geometry :empty
  rule now wins; verified by computed style in Chromium and WebKit at 1440
  and 390.
- panel_reason() restated the raw tier identifier ("tier=best_effort") right
  after the capsule header had said it in words; the tier is now said in words
  everywhere and the header asks for the reason alone (the test pins the whole
  header identifier-free).
- The subagent detail body still prefixed the owner sentence with "Reason:".
- model_wait.js still chose a wait title from the lane fact; a block without
  work carries no placeholder, the always-shown kind keeps "Background thinking".
- The five execution phrases carried from the old prefixed table rendered as
  lowercase fragments; capitalized in both twins, the fixture and the pins.
- The acceptance wake's re-arm reset the candidate's one malformed-control
  repair on every wake; the re-offer keeps a spent repair spent.
- Docs: the depth sentence left the terminal summary row (architecture 01/06),
  the wake re-offer is stated exactly (development 06), the activity-block test
  docstring no longer keys chrome on the lane fact; the recursive querySelector
  patch is restored after the file's tests.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 03:17:42 +03:00
Ouroboros
ac17b70cca Say only what the code knows: neutral cause sentences, exact-name dedupe
Roast round 1: four cause sentences claimed more than the record proves —
"too few reviewers could be read" is false when the reviewers were read and
the cycle cap refused the re-authored answer; "failed before any reviewer
answered", "after it was reviewed" and "the review was skipped" likewise.
They now state the cause neutrally. The X › X dedupe compares the two names
exactly (a prefix rule folded "Art" into "Arthur"). The JS test that parsed
the Python table with a regex was a second SSOT parser and is gone; the
shared parity fixture is the pin. The long-work sentence says "one message
saying what I will check and why"; the promote description names the queue
slot, admission and reviews an independent task gets; send_user_message says
"card". Outcome honesty is shorter than the original (prompts/SYSTEM.md
24391 -> 24289 bytes). The configuration chapter no longer says direct turns
are not named.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 02:51:35 +03:00
Ouroboros
148551ff2a Key the chrome on work, not on any content: no attribute, no compact CSS
Roast round 1 (three lanes): the content predicate counted a bare non-Done
ending as content, so a greeting that failed with no tool call would have
been offered "Turn into project" — outside the owner's rule that a turn
without tools gets no card with actions. blockHasWork now names the work a
block stands on (an always-shown kind, a review group, a child, a non-receipt
row, a tool error); presence keeps the non-Done term. The data-chrome
attribute and its three CSS rules are gone: every attention-only state already
shows its own chip (Waiting…, Cancelling…, Failed), so the only thing a block
without work lacks is a title placeholder and the conversion control — one
unconditional rule hides the empty title.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 02:51:35 +03:00
Ouroboros
409fc7074e Say the cause of a task in the owner's words, once
Owner-facing task rows carried the machine underneath them: a
"Reason:"/"Acceptance:" label in front of a raw code, the stored reviewer
rationale copied into Main, a 240-char cut of the model's own answer, and a
"role=…; parent=…; root=…; project=…" preamble plus a tool name in the Project
thread. The row now states one sentence from a single shared cause table, and
every raw code, stored rationale, template excerpt and ledger identifier stays
where it belongs: the typed row fields, the card, task_results and Logs.

TASK_CAUSE_PHRASES is the one table, keyed on the code alone, in
ouroboros/project_dialogue.py with a byte-identical twin in
web/modules/log_events.js; outcome_phase_parity.json carries a case per
sentence and pins both languages, a second test compares the tables directly,
and the completeness gate now also scans the acceptance vocabulary. A code
with no sentence still stays raw.

Row shapes: Main is "<label> · <word>" plus the sentence and the invitation
into the Project, keeping only the labelled salvage excerpt; the Started row is
one line; the Project-thread row names the outcome and the lineage in words and
drops the id soup, the depth audit and get_task_result. A task whose name is
the project name no longer renders "Launch › Launch". SYSTEM.md's outcome
honesty, the improvement-note header and two plan-review disclosures say the
three endings in words instead of the reviewers' JSON identifiers.

prompts/SYSTEM.md: 24290 -> 24391 bytes.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 02:45:57 +03:00
Ouroboros
4231cf059b Let the block's content choose its chrome, not the lane that ran it
PR #942 keyed the chat block's chrome on the host's lane fact: a direct
conversation turn rendered as a compact activity block with no Done chip,
no title and no "Turn into project", even when it was an 18-tool research.
The owner withdrew that on 16.09: real work is a task card, a greeting with
no tool call is nothing.

One predicate, blockHasContent, now names what the block stands on — an
always-shown kind, a review group, a child card, a non-receipt row, a tool
error, a non-Done outcome — and selects the chrome: a content block is the
task card whatever lane produced it (title, chip, conversion in Main unless
its origin is bound); a block that exists only for open attention keeps the
compact form under data-chrome="compact". blockVisible reuses the same
predicate, so presence and chrome cannot drift. The lane fact stays on the
record for the header pill only (a direct turn keeps the census verdict
beside its block) and no longer blanks the title or gates the button.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 02:36:25 +03:00
Anton
858b0eb0c3 Merge current upstream into the Presence workspace change
Preserve the reviewed feature while moving its documentation into the new reference-book chapters and regenerating their inventory source hash.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-16 01:08:07 +03:00
Anton
76d54c959d Bind Presence work to an owner-selected folder
Persist an optional working folder in existing profile state and expose it through the owner tool and Skills card. Carry the admitted folder through turns, promotion and follow-ups while retaining canonical shared memory and preserving legacy profiles without a folder.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 23:42:14 +03:00
Ouroboros
f4ad64b928 Show a peer's message in the receiver's block and offer Stop on a tool-only direct turn
Owner 5=A asked for a task-authored message to be visible where it lands:
the receiving task's timeline. The event reached events.jsonl and the Logs
projection, but the chat reducer rendered it invisible and the supervisor
never pushed it live. The row now carries a bounded preview, is pushed to
the live log stream, and renders as a visible compact row in the receiver's
block.

A direct turn that only called tools offered no Stop until the model wrote
a narration row, because the host-attested marker rode progress frames only.
The turn's own queue proxy now stamps the marker on its tool frames (the
same by-value rule that carries the lane fact), and the client grants Stop
from any stamped log frame; a turn that neither narrates nor calls tools
keeps no block to hang a Stop on. The lane key is declared in ChatOutbound.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 23:30:00 +03:00
Ouroboros
16d7b04382 Let a direct turn's own frames name its lane before the census does
A live tool or progress frame from a direct conversation turn minted its
block as a managed card until the activity census listed the turn: in that
window (the first tool call landing before the 3-second census read) the
block wore a Task title and a "Turn into project" control the V1 design
removed for direct turns. The turn-scoped event queue already stamps the
turn's chat onto its own task-scoped events by value, because the turn's
registry entry dies before the supervisor drains them; the lane fact now
rides the same rule (a stamped task_done keeps its own value), the progress
delivery carries it into the live frame, and the client settles the lane
from any stamped frame instead of waiting for task_done or the census.

docs/DESIGN.md: a Presence turn is a direct turn and follows the block
rules; only Background consciousness is an always-shown kind.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 22:41:03 +03:00
Ouroboros
1134a09d64 Read Stop from one owner so a block never stands on a Stop it hides
WP-G2 named tests/test_s3_task_control_browser.py::test_s3_chat_card_dropdown_hurry_and_soft_stop
(timeout on `[data-cancel-run]`) as a candidate regression. It is not: the test
passes at its last-touch commit 02e76e741 (2026-08-30) and fails identically on
the sprint base c306b40d8 and on the candidate. Its premise (a replayed
progress row with the host-attested marker offers Stop with no live source) was
retired by 54dfdc727 (2026-09-09, "Activity unconfirmed": a replayed card
offers Stop only once the census or its durable record vouches for the root),
and the fixture's server never lists `live-root`. The same replay with a census
that lists the root shows Stop on the managed card and on the direct block
alike (web/tests/chat_activity_block.test.js pins both), so V1's "Stop stays
reachable while a turn runs" holds on the candidate; the test-vs-09.09 fork is
reported to the owner, not decided here.

What the candidate did get wrong is a split derivation: blockVisible kept a
block for `cancelableTaskIds.has(id)` while the control read the marker
through cancelRunEligibility with the unconfirmed/unavailable facts, so a block
could stand on a Stop it hid. `stopEligible` is now the one reading both use.
The click path's completion-won branch revokes through the existing owner
(revokeManagedTaskCancelAuthority) instead of a second copy, and the dead
`record.cancelable` writes (never read) are gone; chat.js lands at 192,200
bytes, under WP-G's 192,234.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 21:39:14 +03:00
Ouroboros
212f220261 Make an addressing call a receipt row so an addressing-only turn keeps no block
Owner decision 11.09 (2A): a turn that only addressed work (promote_chat_to_task,
route_to_project, steer_task) draws no block; the typed annotation on the owner's
message is its receipt. WP-G made every successful tool call a content row and
deleted the client tool-name list (correct, P5), but replaced the deleted rule
with nothing, so "turn this into a project" showed a direct-turn block with one
promote row beside the annotation and the managed root's own card: exactly the
redundancy 11.09 removed (tests/test_chat_addressing_browser.py, promote_only).

The host states the fact once. tools/control_events.py owns the routing-verb
table (ROUTING_VERBS): the typed action on task_done reads its event side, the
live tool-call frames read its tool side (`routing_action` on tool_call_started
and every tool_call_finished producer), and task_tool_metrics counts the calls
through it (`routing_tool_calls`, carried by the task_metrics event, the
authored summary row and the history replay). The client marks such a row
`receipt` (chatView, the timeline item, patched back to content by a failure
frame) and blockVisible's content term skips receipt rows; the replay summary of
a turn whose recorded calls were all addressing calls, without error, is a
receipt row too. A recorded tool error is content on its own
(`record.toolErrors`), which makes the V1 replayToolErrors term explicit. No
client list of tool names decides presence; the row still renders inside a block
that exists for other reasons. DESIGN.md, the web-UI chapter and the
anti-pattern chapter ("an open default behind a closed exception list") state
the rule in present tense and name the host stamps.

Size: loop_tool_execution.py stays at 1500 lines by reusing `_tc_args` for the
started-frame arguments instead of a second best-effort parse; chat.js
192,234 -> 192,349 bytes, paid back in the Stop commit.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-15 21:39:14 +03:00