Phase 5 follow-ons: trace_id thread, task.* push events, Q5 parity, desktop task board - #44233
Closed
claw3649 wants to merge 56 commits into
Closed
Phase 5 follow-ons: trace_id thread, task.* push events, Q5 parity, desktop task board#44233claw3649 wants to merge 56 commits into
claw3649 wants to merge 56 commits into
Conversation
…res in the UI Model-resolution reliability and picker UX, including the corrections found in review of the in-flight batch: - model_switch: discover keyless local endpoints (ollama/llama.cpp/LM Studio) so `ollama rm`'d models stop lingering in the picker — but only when no explicit `models:` subset is pinned, preserving an intentional narrowing (parity with the custom-providers section; fixes 2 test regressions). - models: bypass the disk cache for custom/local providers (always live), falling through to the shared stale-beats-empty path so a transiently unreachable endpoint keeps its last-known-good list instead of going empty. - model_metadata: pin local Gemma 4 26B to its served 64k num_ctx ahead of the /api/show GGUF-max probe so the token budget can't over-run and silently truncate; reconcile a stale cache entry in place instead of re-probing every call (the previous guard never converged). - web/desktop picker: await the /model switch and surface a rejected switch (result.error) inline — keep the dialog open, roll back the optimistic model, toast benign advisories — instead of closing as though it worked. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- launchd_restart: try a graceful SIGUSR1 self-restart before terminate, with a hard-stop fallback; bind the force-kill to the freshly-resolved live PID (not the captured one) so PID reuse can't SIGKILL an unrelated process. - hermes launcher: re-exec into ./venv/bin/python on Python <3.11, but only after verifying that interpreter is itself >=3.11 — a stale venv no longer silently bypasses the version guard and crashes later with an opaque error. - tests: cover the SIGUSR1 graceful path and reconcile the terminate-drain fallback test with the new graceful-first behavior. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ontract Phase 2 of the Central Brain + Action Runtime design (docs/architecture): a single honest-status schema the gateway's exec handlers funnel through. - contract: ExecutionTask / ExecutionResult / Status / ErrorType / ExecError / SideEffect / NeedsInput / Constraints. `outputs` is a lossless payload bag so each handler's native shape round-trips back to its exact wire dict. - adapters: shell / cli / plugin / slash, each a pure (to_result, to_wire) pair with a byte-identical round-trip guarantee. - tests assert the round-trip identity (the byte-compat oracle). Pure dataclasses with no dependency on the gateway (server imports this, not the reverse). Nothing consumes the rich fields yet — that is Phase 4. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Runtime - slash.exec: report a rejected live side-effect (/model, busy) and a raised plugin handler as a structured result.error (additive — TUI/web that read only output/warning are unaffected), keying off _SlashSideEffect.kind rather than prefix-matching the message text. - shell.exec / cli.exec / slash.exec / plugin path now build an ExecutionResult and render it back through the adapters — byte-identical wire, so the three frontends need no change. A non-zero exit is an honest FAILED result whose wire shape (result.code) is unchanged. - desktop typed `/model` path: surface result.error instead of rendering a rejected switch as a success line; add error? to SlashExecResponse. - add honest-status regression coverage for the plugin error field and for the already-honest shell.exec / cli.exec paths (none existed before). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A design doc for separating the Orchestration Core (reasoning/planning/memory) from the Action Runtime (execution/tool-use), grounded in the current Hermes gateway. Covers the decided terminology (OpenClaw = compat/reference only), the ExecutionTask/ExecutionResult contract, the phased migration (1a honest-status → 2 unified path → 3 SessionState + Brain-host → 4 intent/idempotency → 5 multi-runtime), cross-cutting rules (additive-first, test-first, observability, idempotency), and a decision log tracking what has landed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n sites Phase 3 Step 1 (Central Brain + Action Runtime design, §11). The per-session state was an untyped dict at _sessions[sid] (~58 refs, ~30 keys, locks floating as dict entries). Introduce a SessionState type to anchor the decomposition. SessionState subclasses `dict`, so every existing session["key"] / .get / .setdefault / "k" in session call keeps EXACT dict semantics — zero handler churn. A safety analysis confirmed the gateway has no membership test, bare pop, or iteration that relies on the session value dict, and that absent keys must stay absent (a dict subclass gives that for free, so no field defaults need tuning — notably agent_ready stays absent on the eager path and personality keeps its config fallback). Typed @Property accessors (history, history_lock, running, agent, …) let handlers migrate to attribute access incrementally in later steps; the locks stay dict items until the subscript sites are converted. Wraps the two real creation literals (_init_session, session.create); test-constructed plain dicts still work since SessionState IS a dict. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
SessionState type introduced and committed; note the next step (Step 2 lock-core methods). Also reflects the verified AIAgent instantiation-site count. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…robe Generalizes the root cause behind the Gemma4-26B 64k pin: Ollama's /api/show reports the GGUF training max, which over-states the window a LOCAL server actually serves (the user-set Modelfile num_ctx) and silently truncates conversations once they outgrow it. Steps 2b and 5e now resolve local endpoints num_ctx-first (query_ollama_num_ctx) while hosted servers keep the GGUF-first probe (their users can't set num_ctx); discovered values persist under the same lmstudio cache exclusion the sibling steps use. The explicit Gemma pin stays as a tested guard above these steps. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Opening the picker live-probes every custom/local endpoint (keyless-local discovery in sections 3/4 and the live "custom" branch of provider_model_ids); with a server down each open burns the full HTTP timeout. Add a 30s in-process memo around fetch_api_models — keyed by endpoint + credential fingerprint and pinned to the exact function object so monkeypatched tests stay hermetic (plus an autouse clear in tests/hermes_cli). Empty results and exceptions are never memoized, so a down server still retries on the next open and the stale-beats-empty disk-cache semantics are untouched. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…istry Pre-existing cross-file pollution: the dispatch-pool tests overwrite entries in the module-global _methods registry (slash.exec, session.compress, …) without restoring them, so four session.compress/snapshot tests in test_tui_gateway_server.py fail whenever both files run in one process. The server fixture now snapshots and restores _methods in place (same dict object) plus the directly-assigned module globals (_real_stdout, _hermes_home, _cfg_cache/_cfg_mtime/_cfg_path). Tests-only; both files also stay green alone. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 Step 2 (Central Brain + Action Runtime design, §11). The compaction snapshot + compare-and-swap, the turn-release finally, and the one-shot agent-build guard now live as SessionState methods — snapshot_history, commit_compaction, begin_turn, end_turn, build_once — each a verbatim lift of the server.py block it replaced (same lock, same .get defaults, same order). Five gateway sites migrated; sites whose critical sections interleave extra work with the CAS (the prompt.submit busy-gate + truncate, the poller gates, bare running-flag cleanups, the mid-turn history CAS that logs under the lock) stay deliberately inline to preserve atomicity, each noted in place. Test helpers that feed the migrated paths now construct SessionState (a dict subclass, so semantics are unchanged); 9 unit tests pin the method contracts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…esult wire
Phase 4 (Central Brain + Action Runtime design, §11): make the rich
ExecutionResult consumable.
- slash.exec accepts an optional task_id and echoes it additively (the wire is
byte-identical when absent — existing protocol tests pass unmodified).
- result_to_wire_rich renders the full contract: task_id, status, outputs,
error {type, retryable, message}, side_effects.
- New task.submit RPC: pilot scope intent="slash" only (reversible commands,
e.g. /model), other intents → 4030. The slash.exec body is extracted into a
shared _slash_exec_core returning the ExecutionResult; slash.exec renders the
legacy wire exactly as today, task.submit renders the rich wire.
- Idempotency: a locked in-process store (10-min TTL, 1024-entry cap) replays
the recorded result with replayed=true instead of re-executing; task_id
defaults to a fresh uuid4.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-flight token Phase 4 race fix (from the PR review): selectModel applied optimistic switches with no in-flight correlation, so a slow switch A failing after a faster switch B succeeded rolled the store back to the pre-A model, clobbering B. Each selectModel call now captures a monotonically-increasing token; once a newer call starts, the older call is stale — a stale success commits nothing and a stale failure skips the store/cache rollback (the snapshot predates the newer switch) while keeping the failure toast so the user still learns their requested switch failed. refreshCurrentModel gains an optional staleness guard used by the persistGlobal paths; external callers are unaffected. The slash.exec request also carries an additive task_id for forward correlation; staleness is decided purely by the local token. Two race tests cover both interleavings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sion log Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…us surfacing Upstream moved the web model picker's chat path from slash.exec to config.set and added the expensive-model confirmation flow (pendingConfirm). Resolution keeps upstream's architecture and folds our honest-status work into it: all three modes (standalone onApply, gateway config.set, legacy onSubmit fallback) funnel through one awaited try/catch — a failed switch surfaces inline via submitError and the dialog stays open; a benign post-switch warning fires the new onWarning prop, which ChatSidebar toasts. The now-dead slash.exec onModelSubmit path is removed with upstream. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ttributes Phase 3 Step 3: 107 square-bracket sites in tui_gateway/server.py now use the typed accessors for the 12 hot fields (history_lock=25, session_key=22, agent=17, running=11, history_version=8, history=7, the rest 2-3 each). Mechanical and behavior-preserving by rule: only ["x"] reads/writes were converted — every .get()/.setdefault()/.pop() stays untouched because absent-key semantics differ (None/default vs KeyError), and non-session dicts (DB rows, payload/info dicts) are excluded. Tests that inject plain-dict sessions into the migrated paths now wrap them in SessionState (a dict subclass — no behavioral change to the tests themselves). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 3 Brain-host proof-of-life (design §11 3c/3d): agent/brain_host.py hosts AgentSpec + a process-singleton BrainHost whose build_agent() constructs the SAME AIAgent the call site would (parity is asserted by test, not assumed). _make_agent now assembles its kwargs once and, only when HERMES_BRAIN_HOST=1, routes through the host (lazy import inside the branch — default-off adds zero import cost, proven via sys.modules in tests). V1 is deliberately the seam only; credential-pool/tool-schema/memory-session sharing arrives as the other ~20 construction sites migrate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ision log Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ns through BrainHost Second and third construction sites behind the HERMES_BRAIN_HOST flag (default off, byte-equivalent when unset): gateway/run.py's per-turn LRU-miss site (intent "gateway-run") and the API server's _create_agent (intent "api-server"). Same assemble-kwargs-then-gate pattern as _make_agent; seven new gate tests (the api-server gate is unit-driven; the gateway-run gate is compile+source asserted — its 1200-line async host method is not unit-drivable). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gistry) Grounded scoping of delegation.*/spawn_tree.*/delegate_tool state (registries, locks, persistence, interrupt trees) and the unified design: one AgentTaskRegistry keyed by task_id speaking ExecutionTask/ExecutionResult, with a 6-step dual-write migration, risks R1-R7 (notably: the AIAgent-internal _active_children interrupt chain must not be replaced), and the maintainer decisions Q1-Q6 required before implementation starts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
build_agent(intent, **kwargs) is the compact form of the HERMES_BRAIN_HOST gate for migrating the remaining AIAgent construction sites. It lives in its own light module (imports only os) so the flag-off path never imports agent.brain_host — the zero-footprint invariant the gate tests assert — and run_agent is only imported at call time, matching the lazy-import style of the call sites it replaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n_host_gate
Converts the 14 construction sites that were still calling AIAgent()
directly to build_agent("<intent>", ...) — exact same kwargs, callable
swap only — and collapses the three pre-existing inline HERMES_BRAIN_HOST
gate blocks (tui_gateway/_make_agent, gateway-run LRU-miss,
api_server._create_agent) into single build_agent calls with their
original intent strings.
New intents: prompt-size, oneshot, cli-background, cli, background-review,
curator, acp, cron, compress, history-hygiene, gateway-background,
feishu-comment, tui-background, preview-restart, batch, run-agent-cli,
delegate.
Deliberately untouched: cli.py's lazy AIAgent wrapper (re-export shim),
scripts/tool_search_livetest.py (dev script), and everything around
_active_subagents / spawn trees / interrupt handling in delegate_tool.
Tests: 4 unit tests for the gate helper (flag off/zero/on + kwargs
parity) and a parametrized site-table source check covering all 16
files / 20 intents, replacing the old grep-based gate-exists test whose
marker strings the gate collapse removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…scripts to typed attributes
Adds 10 property accessors to SessionState (attached_images,
pending_title, image_counter, show_reasoning, tool_progress_mode,
tool_started_at, personality, model_override, explicit_cwd,
edit_snapshots) with exact self["key"] read/write semantics, and
converts every server.py subscript on those keys to attribute access.
Two sites deliberately stay as subscripts: session["model_override"] in
_apply_model_switch (one caller passes a plain {"agent": None} dict on
the no-session global-switch path — the isinstance guard there exists
for exactly this) and session["_finalized"] (single private marker).
Accessor-sync and write-through tests extended to cover the 10 new
accessors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…3 Step 4 in the decision log Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…stions Q1-Q2, Q4-Q6 Q3 (Status.RUNNING placement) stays open pending an impact sweep the maintainer requested: a consumer census of Status/ExecutionResult and proof that record-level TaskStatus loses no running-task visibility. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sus, visibility map, lifecycle) Three-agent sweep: option A touches ~8 sites with one genuine breakage (the idempotency replay store would cache a non-terminal snapshot as the permanent answer) plus silent-success hazards on every legacy wire; option B touches zero existing sites and loses no running-task visibility, since every in-flight view (delegation.status, the subagent.*/tool.* event stream, bg counters) lives outside ExecutionResult. Recommendation recorded as option B, pending the maintainer's ruling. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… unblocked Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…itive) The in-process ledger for live and completed agent tasks, encoding the Q1-Q6 rulings: standalone module (not a BrainHost tenant), ephemeral records and replay store, a record-level TaskStatus enum carrying RUNNING while contract.Status stays terminal-only, and method-private storage so a shared backend can replace the dicts later. Nothing imports it yet — Step 2 dual-writes from delegate_tool. The interrupt path resolves the stored agent (weakref or direct) and calls agent.interrupt() outside the registry lock, the same mechanic as interrupt_subagent today. The replay store mirrors _TASK_RESULTS semantics (600s TTL, 1024 cap) ahead of the Step 5 fold-in. 17 unit tests, including two Q3-ruling pins: contract.Status has no RUNNING, and TaskStatus terminal values mirror contract.Status exactly. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d the landed Step 1 path Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… Phase 5 Step 2 Each child registers an AgentTaskRecord (intent="delegate", weakref agent_ref) right after _register_subagent and completes it in the same finally block that unregisters the legacy dict entry, mapping the existing per-child entry to a terminal ExecutionResult: completed → SUCCEEDED, interrupted/timeout → FAILED + ErrorType.TRANSPORT (non-retryable, per the doc ruling), failed → PROVIDER_ERROR, escaped exception → INTERNAL, died-without-entry → complete(None) → FAILED. Both registry calls are try/except-guarded so a registry bug can never break a real delegate run. _active_subagents, _active_children, heartbeat and timeout paths are untouched — the registry is a parallel ledger until the Step 7 cutover. 8 new tests; delegate suites 172 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lds into the registry — Phase 5 Step 5 _task_submit_delegate validates inputs.tasks, resolves the session like slash, registers a guarded parent AgentTaskRecord (weakref agent_ref — task.status sees the batch, task.cancel interrupts into the engine's interrupted-partial path) and calls the existing delegate_task engine unchanged. _delegate_aggregate_to_result maps the aggregate per the Q4 ruling: all completed → SUCCEEDED, mixed/interrupted → PARTIAL with the per-child breakdown verbatim in outputs (Core re-plans only unfinished children), none → FAILED typed by the Step-2 convention; engine exceptions are FAILED results, never protocol errors. No dispatch change needed — task.submit was already in _LONG_HANDLERS, so intent="slash" ordering is untouched; the R6 synchronous-blocking constraint is documented on the handler. Idempotency fold: _task_result_replay/_task_result_store now delegate to registry.recall/remember (same 600s TTL / 1024 cap), each guarded to degrade to a cache miss rather than a broken run. Protocol tests for both this and Step 6b land in the next commit (single contiguous test hunk). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h the legacy index — Phase 5 Step 6b _read_spawn_tree_tasks mirrors the legacy reader's lenient line handling; _registry_task_entries dedupes by task_id preferring the legacy TUI-assembled entry (Q5 ruling) and maps registry snapshots onto the existing list-entry shape. Sessions without _tasks.jsonl produce byte-identical output (pinned by test). Carries the protocol tests for both Step 5 (7 tests: delegate happy path, Q4 PARTIAL pin, engine exception, param errors, no-agent error, delegate idempotency replay) and Step 6b (4 tests: legacy-only byte-identical, registry-only, dedupe preference, corrupt-line skip). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 7 findings closed R1 terminal-state guard: a second complete() returns False without mutation, double persist, or replay overwrite (a late bg failure can no longer falsify a recorded success). R2 bounded retention: terminal records evict oldest-first past RECORDS_TERMINAL_CAP=1024; RUNNING records never evicted. R3 interrupt(task_id, reason=None): forwards the reason (AIAgent.interrupt accepts an optional message) and swallows agent exceptions, restoring legacy interrupt_subagent parity — a bare exception could previously kill the inline stdio dispatcher. R4 replay timebase back to time.monotonic(), matching the _TASK_RESULTS store the fold replaced. R5 the idempotency fold stores the rich result wire, not the record snapshot. R6 complete() builds one snapshot under the lock and reuses it for replay and persistence; new locked readers get_snapshot/list_active_snapshots for the RPC layer (torn-read fix). R7 _tasks.jsonl appends are single unbuffered byte writes so large rich-result lines cannot interleave between threads. Plus update_progress(task_id, tool_count, last_tool) for the live delegate progress relay (G8). 8 new unit tests; one fixture realigned to the R5 shape change (ids now come from the ExecutionResult, as in production). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ep 6 wiring — 9 findings G1 intent=delegate now claims the session busy gate (begin_turn/ end_turn, same 4009 'session busy' as prompt.submit) — a batch and a user turn can no longer run concurrently on the same AIAgent with cross-coupled interrupt state. G2 a retry of a still-RUNNING task_id is rejected (4034) instead of re-executed. G3 the parent agent's interrupt flag is cleared after the batch (verified NOT auto-reset at conversation start), so a task.cancel mid-batch cannot abort the user's next turn. G4 session_id is now wired at all three record sites (bg/ preview, delegate parent, delegate children via parent_agent.session_id) — Step 6 persistence actually runs in production, proven end-to-end by new _tasks.jsonl tests. G5 the idempotency-test isolation helper resets the registry singleton (the old helper poked a dormant dict). G6 the registry-first subagent.interrupt forwards the legacy 'Interrupted via TUI' reason. G7 only the spawn-pause rejection maps to DENIED/ retryable; other engine errors are INTERNAL non-retryable. G8 the TASK_TOOL_STARTED relay mirrors tool_count/last_tool into the registry so task.status shows live progress. G9 task.status/task.list read locked snapshots. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…outcome, and the deferred gaps Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ry ledger paths Closes deferred review gap 3: a registry-only spawn_tree.list entry points its path at the session's _tasks.jsonl, which the legacy load path could only reject. The handler now detects the ledger basename, parses it leniently, selects by the optional task_id param (else the last line), and synthesizes a payload in the exact spawn_tree.save shape — status vocabulary and field mapping derived from the actual TUI consumer (spawnHistoryStore/agentsOverlay): succeeded→completed, partial→interrupted, result text into the rendered summary, ms-epoch startedAt. Legacy snapshot paths flow through the pre-existing code byte-identically (pinned by test). 5 new protocol tests. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/tasks lists active records via task.list (short id, intent, status, age, tool progress, truncated goal); /tasks status <id> renders a full snapshot incl. terminal result; /tasks cancel <id> reports the registry verdict honestly (bg_*/preview_* runs phrase found:false as 'don't accept cancel yet', not as an error). Behavior note: 'tasks' was previously an alias of /agents (spawn-tree overlay); it now resolves to this registry board — /agents itself is untouched and keeps the overlay. Additive wire types mirror task_registry.snapshot()/result_to_wire_rich. 10 vitest tests follow the existing slashParity pattern; type-check and eslint clean, full ui-tui suite at its pre-existing baseline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ush events, Q5 parity fields Three additive pieces on top of Phase 5, all absent-when-unset so every pre-existing wire/snapshot/ledger shape stays byte-identical (pinned by exact-dict tests): trace_id (doc §12 — 'add at contract time, retrofit is expensive'): threads an optional caller-supplied id across ExecutionTask → ExecutionResult → rich wire → AgentTaskRecord → _tasks.jsonl, echoed by task.submit for both intents. Never synthesized; None = pre-trace caller. task.started/task.completed push events: the registry gains a single-observer seam (set_observer — called outside the lock with the prebuilt snapshot, guarded so an observer bug never breaks the ledger; action_runtime still imports no transport). The gateway installs the observer and routes through _emit with the same parent-session rule as background.complete; records without session_id emit nothing. Clients can stop polling task.list. Q5 parity fields (sunset progress): label, tools tail (deduped, capped, fed by update_progress), and error summary on the record — populated at the existing register/complete sites (delegate children label=goal, bg/preview label=goal[:120], delegate parent gets a summarized batch label). spawn_tree.list/load prefer the richer fields. The doc's Q5 block now lists the exact fields still TUI-only. Suites: action_runtime + protocol + tui_gateway + dualwrite all green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…servability branch, Step 7 prep Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sh refactors, keep honest-status surfacing Upstream restructured executeSlashCommand into a slash-action table with a generic runExec path (and then made slash commands registry-driven in #42351); /model-with-args now rides runExec, so our result.error honest-status check lives there — covering every exec-style command instead of just the typed /model path. The refreshCurrentModel guard keeps BOTH protections: upstream's live-session-owns-the-footer guard and our in-flight staleness token. The model-controls test file unions upstream's two refreshCurrentModel tests with our four selectModel rollback/race tests (6/6 green under --environment jsdom). Desktop src/ suite shows the byte-identical failure set to pure origin/main (6 pre-existing); python gateway suites green. Note: this replaces an earlier broken push of the same resolution — a mid-merge stash for a baseline measurement silently dropped MERGE_HEAD, so 46a668be2 landed as a single-parent commit with partial upstream content. Reset and redone as a true merge; baselines are measured in detached worktrees from now on. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e up the stack Single conflict: config.set reasoning_effort gained _persist_live_session_runtime + a session.info emit upstream while this branch had converted the same lines to SessionState attribute access — resolution keeps both (attribute style per Phase 3 Step 3). Suites: 489 green (single known browser-hint env failure). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… closes review gap 1 delegate_task gains an optional registry_parent_task_id (default None = byte-identical engine default); when the gateway's intent="delegate" handler passes its task_id, child registry records link to the submitted parent record per the doc's Step 5 ruling. Only the AgentTaskRecord relabels — the legacy TUI dict keeps the engine's _parent_subagent_id, so interrupt propagation, depth display and the spawn-tree overlay are untouched. Non-string values degrade to the engine default. 4 new tests pin both ends of the chain (engine register site + gateway kwarg pass-through). 343 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Session-keyed tasks store mirroring the subagents store: seeded from task.list on every gateway open (doubling as reconnect reconciliation — running rows absent from the live list drop rather than getting an invented terminal status), updated live from task.started/ task.completed (snapshot session_id is authoritative; events without one are dropped, never attached to the focused chat). TaskBoard renders one row per task — status glyph, intent badge, label, tool progress, age — terminal rows linger 60s then drop. Mounted above the spawn tree in AgentsView. 9 store/render tests + canary suites green; tsc and eslint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…op board, measured BrainHost cache Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eview system Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…d client Mirrors the tools-card architecture exactly: seeded once via task.list on the JSON-RPC sidecar after session.create, live-updated from task.started/task.completed frames on the SAME /api/events socket the tools card uses. Rows: status glyph, intent badge, label/goal, live age, tool detail or error; terminal rows linger 60s. Pure reducer helpers extracted dependency-free (web/ has no test runner yet). Typecheck clean; web lint baseline untouched (44 pre-existing problems in unrelated files). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…light key replay, child result ids
WART 1 — session-id namespace split: delegate batch children registered
under the agent's persistent session key while the parent carried the
gateway sid, splitting _tasks.jsonl across two spawn-trees dirs and
emitting child task.* events with a session_id matching no live gateway
session (dropped on per-session WS transports). delegate_task gains an
optional registry_session_id threaded to the child register site;
_task_submit_delegate passes the sid it already resolved. Default None
keeps non-gateway delegate runs unchanged.
WART 2 — in-flight idempotency gap: the duplicate guard only checked
the caller's task_id, so a broken-pipe retry reusing the
idempotency_key with a fresh task_id mid-batch would re-execute.
registry.find_running_by_key + the key stamped on the parent record;
retries now get the same 4034 rejection naming the running task_id.
The after-completion replayed:true path is untouched.
WART 3 — child ExecutionResults now embed the child's task_id (was
None, contradicting the always-carry rule parents follow).
WART 4 — delegate aggregate results entries gain an additive task_id
key so a Core caller can correlate entries to registry child records
without relying on the sa-{index}-{hex} naming convention (all existing
consumers use .get() access — verified by grep).
Found by live dogfooding (dossier on the Step 7 PR). 351 green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ent wrapper Three CLI tests stubbed construction by patching cli.AIAgent — a pure pass-through wrapper the BrainHost migration no longer routes through — so the real AIAgent ran and each test failed on its environment (CI caught this; our targeted macOS runs never covered these files). Investigated each as a potential real regression first: the MCP wait-before-build ordering, the background-thread callback registration order, and the single resolve_runtime_provider call per init are all unchanged in production (the extra counted resolutions came from real AIAgent internals that the old patch merely prevented from running). Test-only fix: patch agent.brain_host_gate.build_agent — the seam production actually uses — with every behavioral assertion unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… idle reaper test_ws_disconnect_preserves_and_repoints_reconnectable_session seeds bare session dicts with no created_at/last_active. The module-level idle reaper reads missing timestamps as 0.0 — idle since the epoch — so the moment handle_ws's finally closes the transport, the session is evictable, and a reaper tick landing in the close-to-assert window pops it: KeyError 'plain'. The 300s tick makes this a once-in-many-runs flake that only bites long-lived pytest processes (CI's per-file subprocesses exit before the first tick), which is why it resisted plain-loop reproduction. A probe driving the production scan body at a fast tick reproduced the exact failure 199/200 before the fix and 0/200 after. Production is immune: every real creation site stamps both timestamps. Test-only: a _fresh_session helper stamps time.time() on both seeded sessions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tyle repoint works After Phase 3 Step 3 the WS-disconnect repoint path sets session.transport = _detached_ws_transport (attribute style); on a plain-dict seed that raises AttributeError, silently skipping the repoint, so test_ws_disconnect_preserves_and_repoints_reconnectable_session saw the session keep its live WSTransport (CI caught this — our targeted runs never ran this file's slice). _fresh_session now returns a SessionState, matching every real creation site. Folds the two reasons the seed must be a SessionState (timestamps + attribute access) into one docstring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #43422 (which stacks on #43039) — kept as Draft until the parents merge; review only the commits past the #43422 merge base (
60fa13a8a,9c568acfa,b0f2f109b).Observability + clients for the AgentTaskRegistry (Phase 5 follow-ons)
trace_idthread (doc §12 — "add at contract time, retrofit is expensive"): optional caller-supplied id acrossExecutionTask→ExecutionResult→ rich wire →AgentTaskRecord→_tasks.jsonl, echoed bytask.submitfor both intents. Absent-when-unset everywhere — every pre-trace wire/snapshot/ledger shape stays byte-identical (pinned by exact-dict tests). Never synthesized.task.started/task.completedpush events: single-observer seam on the registry (called outside the lock with the prebuilt snapshot, guarded — an observer bug can never break the ledger;action_runtimestill imports no transport). Gateway installs the observer and routes via_emitwith the same parent-session rule asbackground.complete. Clients can stop pollingtask.list.label/toolstail (deduped, capped 8) /errorsummary on the record, populated at the existing register/complete sites;spawn_tree.list/loadprefer the richer fields. Remaining TUI-only fields are listed in the design doc's Q5 block.delegate_task(registry_parent_task_id=…)— additive kwarg, default = byte-identical engine behavior; the gateway'sintent="delegate"handler passes its task_id so children link to the submitted parent record (doc Step 5 ruling).task.liston gateway open (doubles as reconnect reconciliation) + livetask.*events (snapshotsession_idauthoritative;unattributable events dropped, never attached to the focused chat). Compact board in the Agents view; terminal rows linger 60 s.Verification: python suites 387 green; desktop store/render tests 9 green, tsc + eslint clean.
🤖 Generated with Claude Code