Repair batch authored by a delegated Claude Fable run; reproduced each finding via a failing test through the real seam.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Keep this version-neutral contribution local. B1/B2/B3 and C1/C4/C5 are partial; C2/C3, exact diff review, full suite and PR are outstanding. Do not merge this checkpoint without final-candidate checks.
Upstream landed its own answer to the same class while this branch was out
(f554260be, 4bc05ffb4): an optional question ignores a habit-filled bound, a
required wait REFUSES `0` and a bound past the task ceiling, and every refusal
names the repair. This branch had clamped instead of refusing and allowed `0`
as "no bound". Released, reviewed semantics win; nothing here re-opens them.
- core.py: the `max_wait_minutes` schema text returns to upstream's. Mine
promised "pass 0 for no bound" and "lowered to the ceiling" — behaviour the
code no longer has, and a schema must not promise what the code will refuse.
- core_artifacts.py: `_validate_wait_bound`, `_wait_bound_note` and the child
receipt are upstream's. Kept from this branch: the quiz refusal is published
through `argument_refusal` (typed `TOOL_ARG_ERROR` plus "The quiz was not
sent.", so a refusal states what did NOT happen), and a blocked link stays a
policy denial via its own marker. Both are orthogonal to the validation rule.
- tests: the assertions that pinned the dropped wording, the value-in-refusal
and "0 waits unbounded / a huge bound is lowered" now pin upstream's actual
contract. The tolerant path stays pinned on the OPTIONAL question, which is
the affordance this class was about.
- inventories regenerated: both source docs merged from two sides, so neither
side's recorded line range and SHA was still true.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Models fill every key of a tool schema. Five handlers refused the empty or
mode-irrelevant form of an optional argument with a text that restated the
rule, the retry kept the same key set, and several of those refusals were
recorded as successful calls, so neither the task nor its reflection could
locate the fault.
- escalate: a wait bound on a quiz that does not wait, or 0 on one that does,
takes the omitted path and the receipt says so; a bound past the task ceiling
is lowered to it; the ignored bound is never persisted with the quiz.
- knowledge_read: an end past the note is lowered to it, 0..0 reads the whole
note, one bound alone is allowed; the returned range is the range delivered.
- delegate_wait: the empty pair (0, "") asks for no checkpoint and the wake
says so; a half-filled pair is refused naming the missing half.
- schedule_followup: a zone beside an absolute run_at is ignored, disclosed
and not stored.
- get_task_result source ranges: an invalid range still returns no text, now
with the complete length and the range received, published as an argument
error instead of ok.
The remaining refusals at these sites are published typed and name the value
received (tools/arg_feedback). The typed-refusal source lint now counts
interpolated marker returns per file, growth-only.
The description claimed the shared wait "ends on the first owner reply". Any
incoming message ends it (a descendant's message, a hurry, a system notice), so
it now says "the first incoming message"; 935 -> 932 bytes against the base,
and the catalog pin is rolled with that number.
The above-ceiling refusal now carries the same "omit it for an unbounded wait"
repair as the other invalid required bounds. The child's receipt discloses an
ignored bound in brackets instead of gluing a second period onto an assumption
that already ends with one. Tests pin the child path, the ceiling repair and
the consumer of the wait fields (the parked wait carries no bound).
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A schema-filling model names max_wait_minutes on a question it does not wait
for. The validator refused that as a contradiction, and in the live log the
asker reacted by switching to blocking questions asked one by one. Naming the
documented default is not a different request: on an optional question the
bound now takes the omitted path and the receipt says it was ignored. A
required wait keeps its refusal, and both refusals now name their repair.
A bounded wait also leaked its deadline into a later unbounded question of the
same tool batch; the wait fields are now reset per question.
The tool description carries the criterion for waiting (irreversible or costly
next step, or a choice that is the owner's to make) and the one mechanical
fact that waiting questions in a batch share one wait. 935 -> 927 bytes.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
- 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>
- A capped tree (Act/Observe = light) cannot schedule a self_worktree child nor land a
system-repo patch through integrate_*: the scheduling gate and the integration's
protected-path gate read the task's effective mode, not the install mode alone.
- A one-shot follow-up the consciousness door refuses (allowance, concurrency) moves
its run point forward by the alarm floor instead of re-firing every supervisor pass.
- The DIRECT lane announces a timed-out bounded wait too (one seam with the pool), and
history replay carries the closed bound (wait_ended_at, no wait_for_answer).
- An empty consciousness model slot keeps the role's own local flag when it differs
from Main's (В25=B); /api/state computes the alarm snapshot on the worker thread;
a resumed campaign drops its stale pause reason; the caller's enabled flag wins in
the status projection; wake_gate_open takes no chat id.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
A root that must have an answer could only wait forever. escalate now takes an
optional max_wait_minutes: the task still parks at the completed-tool boundary,
and if no answer arrives within that window it resumes on its own with a
[SYSTEM NOTICE] instead of holding the task open indefinitely. The card is
untouched by the timeout — it stays open, and a later answer reaches the task's
chat as an ordinary owner message (the half that landed in the previous
commit).
The bound is validated by the shared quiz validator (a whole positive number of
minutes, only with wait_for_answer, capped by the task's absolute wall-clock
ceiling — beyond it the ceiling ends the task first, so a larger bound would be
a promise the runtime cannot keep) and refused as QUIZ_WAIT_BOUND_INVALID.
It travels as an ABSOLUTE instant in the owner-wait checkpoint
(wait_deadline_at plus the minutes it named), so a planned restart resumes the
SAME bound instead of granting the full wait again, and the cold continuation
emits the same notice as the warm one. Both continuation callbacks now return
"owner_input" or "timeout": the direct loop breaks on the deadline only AFTER
control_reason() is consulted, so Stop, cancel, the task deadline and the
absolute ceiling keep precedence; the pooled worker turns the spent bound into
a second reason for the SAME resume request the mailbox already sends, from
inside the parked gate, with no new scheduler (disclosed: that resume is a
request, not a guarantee — the grant can still be refused, and the hard axes
end the task in that case).
No new quiz or wait state: the row resumes with the additive
resume_reason: "timeout", which the activity projection now carries so the
project question pointer keeps reading "Answer needed" — the question was never
answered. The notice itself names the recorded assumption when there is one and
otherwise says plainly that no answer is not consent. The bound is deliberately
invisible on the cards and in Telegram (DESIGN: no timers, no countdowns); the
escalate receipts now state what the task actually agreed to.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Use short pytest labels without reducing large Unicode inputs, block provider/catalog calls at their existing seams instead of OS-local sockets, and decode UTF-8 task-state fixtures explicitly. Retain browser/container facts on Docker UI failure without changing the acceptance condition or timeout. Seven test files only; production behavior is unchanged. Focused combined Windows-related modules: 72 passed.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Preserve execution-limit causes and separate Telegram host notices from the assistant silent chain. Remove the obsolete unconditional continuation instruction, align reviewed schema pins, and retire duplicate extraction size policy. Correct browser test flow control and synchronize final release carriers. Publication awaits final exact-candidate tests, review convergence and CI.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
Connect ordinary chat actors to the existing completed-tool wait and control/budget tail. Preserve host notices in batch child handoffs, correct reviewed integration test contracts, and retain the queue-deadline repair and stronger cancellation tests. Consolidate stale/duplicated documentation without new gates or stores. Exact-candidate verification and external synthesis reviews follow before publication.
Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>