diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index c80eedae9..4976052f9 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -141,7 +141,7 @@ server.py (Starlette+uvicorn) ← HTTP + WebSocket on configurable host:port (de ├── delegate_output.py ← Staged-output + read-receipt cluster for delegated runs (extracted from tools/delegate.py for the module-size gate; delegate.py re-exports it so sibling code and tests keep one name): `_stage_full_output` writes the WHOLE terminal detail atomically under the task drive (`delegated_runs/.json`, sha256 + byte length recorded), and `acknowledge_staged_output_read` — hooked into `read_file`'s task_drive path — credits DELIVERED character ranges until contiguous EOF coverage, then writes the once-per-run durable `delegate_run_output_consumed` row (disclosure, never a gate) ├── delegate_containment.py ← Containment verification for one delegated run (extracted whole from tools/delegate.py for the module-size gate, v6.90.0): `_widened_access` reads the ENGINE-derived effective access back off the run and names a wider-than-asked profile; `_home_isolation_breach` verifies the applied scoped HOME off the attempt artifacts against TWO EXACT FACTS (phase A3, 2026-08-11): a recorded `harness_home_isolated: false`, or an applied home EQUAL to the operator's own. Nothing else is enforced — a home NESTED under `$HOME` is the engine's own layout on boundary-less hosts and flows to the disclosed-unconfined path (`home_nested_under_operator_home` reports it, and the evidence reader keeps `verified: false` plus the durable unconfined row even when an OS boundary WAS recorded, so a nested home is never relabelled as isolation); absence of either fact stays absence, reported as unproven rather than enforced ├── delegate_progress.py ← Low-level bounded Claudexor poll/progress observation used by supervision: journal advances stream to the human and update the cursor, but are not themselves model-wake events. `poll_bound` owns each transport read bound and the existing transient Git-object retry; `delegate_supervision.py` owns renewal and wake semantics above it. - ├── nanny_pacing.py ← Pure/process-local configured-session pacing marks. Only genuine ACTS of delegation reset the burn baseline (`BASELINE_RESET_TOOLS`: `delegate_start`/`schedule_subagent`); supervision verbs (`delegate_wait`/`delegate_answer`/`delegate_cancel`) advance the round baseline while dollars keep accumulating; every other coordination verb is observed as a phrasing input for the reminder and never buys metered silence (charter, owner 2026-08-28 — the poltergeist pattern was tens of metered rounds each "paid for" by a cheap tree_read/verify_and_record baseline reset). Decides no topology and forces no leaf. + ├── nanny_pacing.py ← Pure/process-local configured-session pacing marks. Only genuine ACTS of delegation reset the burn baseline (`BASELINE_RESET_TOOLS`: `delegate_start`/`schedule_subagent`); supervision verbs (`delegate_wait`/`delegate_answer`/`delegate_cancel`) advance the round baseline while dollars keep accumulating; every other coordination verb is untracked — no meter reset, no separate observation; the unified reminder wording counts supervision/coordination rounds toward the burn, so coordination never buys metered silence (charter, owner 2026-08-28 — the poltergeist pattern was tens of metered rounds each "paid for" by a cheap tree_read/verify_and_record baseline reset). Decides no topology and forces no leaf. ├── delegate_interactions.py ← Interactive-question cluster for delegated runs (extracted from tools/delegate.py for the module-size gate; delegate.py re-exports it): the process-local reported-question memo (`_REPORTED_INTERACTIONS` — a known question does not re-trigger the immediate return; popped on a delivered/already_resolved answer so the next wait re-reports promptly), the bounded inline projection `_bounded_interactions` (EVERY harness-authored DISPLAY scalar bounded — question, options, header, source, timestamps — cuts counted; the answer keys ride whole, see below), the immediate typed `waiting_on_user` payload (full set spills whole to the task drive under an interaction-addressed immutable name `..interactions.json` with a sha256/size receipt; a compact `advances` ride-along keeps the cut-short window's journal sequence), and `_delegate_answer` (strict pre-POST row validation — string-only labels, non-empty label-or-freeText per row, no coercion; the answer keys `interaction_id`/`question_id` ride WHOLE, never truncated; engine-typed outcomes relayed verbatim; only a PAYLOAD-SEMANTIC 4xx — 400/409/413/422 — maps to the `rejected` shape, a spent subscription window is the distinct `subscription_window_exhausted` outcome carrying `reset_at`, and `delivery_unknown` is reserved for transport death/5xx plus every other non-definite status and carries a bounded detail re-read; a `timeout_at`-bearing question benign-declines at the engine timeout while `timeout_at=null` waits until answered; an internal monotonic deadline strictly below the ToolEntry timeout budgets handshake/POST/re-read and returns typed on exhaustion without further wire calls) ├── delegate_shared.py ← Shared nanny-verb LEAF (phase B facade split): the single author of the typed delegate refusal (`_fail`), the custody-rooted `_emit`, and run-ownership resolution (`_owned_run` — OWNED/FOREIGN/UNKNOWN replayed from the durable rows). Extracted from tools/delegate.py to break the facade import cycle; one-way seam — the leaf never imports the facade back, and `tools.delegate` re-exports the same objects ├── route_spec.py ← Neutral route primitive shared by Available subagents and reviewer rows: route-kind/target/pin normalization and effort validation, while each semantic owner retains its own public spelling and policy. diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 307c929dc..9eca0a0ae 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -1287,8 +1287,9 @@ Before every commit, verify the following: Metered pacing (`nanny_pacing.py`): the burn baseline resets ONLY on real acts of delegation (`delegate_start`/`schedule_subagent`); supervision verbs (`delegate_wait`/`delegate_answer`/`delegate_cancel`) advance the round - baseline while dollars keep accumulating; coordination verbs are observed for - nudge phrasing but never buy metered silence. `_nanny_route_dispatched` + baseline while dollars keep accumulating; coordination verbs are untracked — + no meter reset, no separate observation; the unified reminder wording counts + supervision/coordination rounds toward the burn. `_nanny_route_dispatched` covers every configured `agent_session` row as well as `executor="harness"`, so the reminders stay armed across mid-run failures. Supervision is not a topology state machine: host code must not infer a diff --git a/ouroboros/nanny_pacing.py b/ouroboros/nanny_pacing.py index af4ab9efc..42b2383be 100644 --- a/ouroboros/nanny_pacing.py +++ b/ouroboros/nanny_pacing.py @@ -19,13 +19,6 @@ DELEGATE_ACTIVITY_TOOLS = frozenset({ # cheap tree_read/verify_and_record baseline reset. BASELINE_RESET_TOOLS = frozenset({"delegate_start", "schedule_subagent"}) -HOST_COORDINATION_ACTIVITY_TOOLS = frozenset({ - "schedule_subagent", "wait_task", "wait_tasks", "get_task_result", "peek_task", - "tree_note", "tree_read", "verify_and_record", "cancel_task", - "discard_child_result", "override_delegation_constraint", "forward_to_worker", -}) - - def note_nanny_delegate_activity( ctx: Any, round_idx: int, @@ -143,7 +136,6 @@ def nanny_burn_phrase(rounds: int, cost: float) -> str: # Compatibility spellings retained on ``ouroboros.loop`` through imports. _DELEGATE_ACTIVITY_TOOLS = DELEGATE_ACTIVITY_TOOLS -_HOST_COORDINATION_ACTIVITY_TOOLS = HOST_COORDINATION_ACTIVITY_TOOLS _note_nanny_delegate_activity = note_nanny_delegate_activity _nanny_metered_since_delegate_activity = nanny_metered_since_delegate_activity _nanny_reminder_due = nanny_reminder_due diff --git a/prompts/SYSTEM.md b/prompts/SYSTEM.md index 64f5d7ce5..2e3fb56fa 100644 --- a/prompts/SYSTEM.md +++ b/prompts/SYSTEM.md @@ -226,8 +226,9 @@ typed rights/deadlines/caps, and may be read-only or acting when the parent explicitly grants that surface. As a session nanny, only real acts of delegation (`delegate_start`, `schedule_subagent`) reset my burn baseline; supervision verbs (wait/answer/cancel) advance rounds while dollars keep accumulating, and host -coordination (children, waits, tree evidence) is observed for phrasing but never -buys metered silence — nor is it evidence that a physical leaf was started. +coordination (children, waits, tree evidence) is untracked — it neither resets +the meter nor silences the reminder, and it is never evidence that a physical +leaf was started. In a CONVERSATION turn (the fast chat lane), real work — anything needing tools, files, or multiple steps — goes through `promote_chat_to_task`: the diff --git a/tests/test_acceptance_substrate_execution.py b/tests/test_acceptance_substrate_execution.py index 12f6b42f1..79f6393f0 100644 --- a/tests/test_acceptance_substrate_execution.py +++ b/tests/test_acceptance_substrate_execution.py @@ -104,10 +104,11 @@ def test_the_section_is_facts_only_no_acceptance_consumer_gates_on_it(): repo = pathlib.Path(__file__).parents[1] hits = [] - for path in (repo / "ouroboros").rglob("*.py"): - text = path.read_text(encoding="utf-8") - if "substrate_execution" in text and path.name not in {"review_evidence.py", "delegate_evidence.py"}: - hits.append(path.name) + for root in ("ouroboros", "supervisor"): + for path in (repo / root).rglob("*.py"): + text = path.read_text(encoding="utf-8") + if "substrate_execution" in text and path.name not in {"review_evidence.py", "delegate_evidence.py"}: + hits.append(path.name) assert hits == [], f"unexpected typed consumers of substrate_execution: {hits}" source = (repo / "ouroboros" / "review_evidence.py").read_text(encoding="utf-8") # The section is written into the packet and never compared/branched on.