Build(deps-dev): Bump @types/node from 18.19.86 to 22.14.0 - #14
Merged
Conversation
Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 18.19.86 to 22.14.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 22.14.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com>
joelteply
approved these changes
Apr 9, 2025
joelteply
added a commit
that referenced
this pull request
Nov 30, 2025
…s/node-22.14.0 Build(deps-dev): Bump @types/node from 18.19.86 to 22.14.0
joelteply
added a commit
that referenced
this pull request
Jun 21, 2026
…#14) (#1719) VDD instrument, slice 1. The always-on persona recorder watches the LEGACY respond() path; the live decision path is the WorkspaceCycle, whose WorkspaceCaptureSink defaulted to Noop — so the mind that actually runs ran UNOBSERVED. (Confirmed by inspecting on-disk fixtures: the only captures are respond()-path turns with systemPrompt:null — not the cycle.) This is why we were reduced to guessing from log lines whether recall surfaced an engram. JsonlWorkspaceCaptureSink records every WorkspaceCycle tick to a per-persona JSONL (`~/.continuum/fixtures/workspace-traces/<persona_id>.jsonl`): every faculty BID incl. losers (recall's bid CONTENT is the engram text the decider saw — the load-bearing field for "was memory present?"), the assembled context that won attention (the RAG the decider actually read), and the decision. Wired into build_workspace_cycle via the existing `with_capture` seam; best-effort (degrades to Noop on FS error, never fails persona spawn — observability is not load-bearing). This is the measuring instrument the rest of the cognition work is validated through (VDD: half the work is harnesses). It's also the prerequisite for DELETING the legacy respond() fallback — you instrument the real path before you remove the dead one ([[no-fallbacks-ever]]: legacy gets deleted, not kept as a fallback; that's the next slice). Own module (cognition/workspace_capture.rs); WorkspaceTraceRecord owns the wire format so it evolves independently of the live Contribution type. 2 tests: bids+context+decision round-trip with content intact; one-line-per-tick append. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
joelteply
added a commit
that referenced
this pull request
Jun 29, 2026
… factory's per-station)
The factory must isolate and REPEAT any cognition phase, not just the final LLM
call. A faculty is `contribute(&Workspace) -> Contribution` — that signature is
the unit of isolation. `replay(ws, only)` re-runs faculties against a GIVEN
workspace (reconstructed from a capture or hand-built): `Some(id)` isolates one
("what did recall surface for this burst?"), `None` runs them all. Each bid is
timed individually (sequential, so per-faculty wall-clock is attributable —
measurement path, not the live concurrent cycle). Deterministic for the same
ws + backends, so mutating ONE field of ws isolates its causal effect.
This is task #14's ReplayFaculty in its truest form — the brick the cu
`cognition/replay` command will sit on. Reliability comes from structure that
verifies every phase, measured, not from model size assumed.
what this catches: replay(Some) must run EXACTLY that faculty + stamp timing,
never leak others; replay(None) runs all. Test green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo
joelteply
added a commit
that referenced
this pull request
Jul 3, 2026
…(June 29→July 2) (#1730) * refactor(cognition): extract the gym grader into one shared module test_grade / grade_rust + helpers were private to cognition/eval.rs. The teacher-episode generator (genome/teach, next slice) must grade write→fix trajectories with the EXACT same verdict the A/B evaluator uses, or a gene that "passes" generation could "fail" eval for grader-shape reasons alone. Per the compression principle (one logical decision, one place): lift the grader into cognition/gym_grader.rs with pub test_grade + extract_code_block, its tests move with it, eval.rs now `use`s it. No behavior change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(genome): genome/teach — the teacher-episode generator (write→grade→fix→pass) Cold-start for the engineering reflex. The genome loop closes mechanically but produced INERT genes because the lever was never raw coding skill — it's the reflex of write code → read the REAL compiler/test error → fix → re-run → answer. You can't distill a reflex absent from the data (5 of 1761 captured turns ever used a tool), so this bootstraps it: a teacher model writes Rust, the SHARED gym grader (`cognition/gym_grader::test_grade`, the same one cognition/eval uses) actually compiles+runs it, the real error feeds back, and it loops to green. Only test-VALIDATED write→error→fix→pass trajectories become multi-turn ShareGPT examples — the grader is the corpus-quality gate, the teacher only affects yield. This is the sanctioned way the genome loop fixes behavior: curate the LEARNING corpus by an objective scorer, never puppet live output. Non-disruptive — writes a dataset, never touches the live :58057 serving lane. Procedure is never the artifact: the reflex is LEARNED from these trajectories, not hardcoded as a run-N-times loop. Feed the dataset to genome/job-create to forge the gene. - `genome/teach` (stateless, Privileged): resolves the teacher adapter the canonical way (global_registry + resolve_model, mirrors generate_response), defaults to the locally-served model so it runs with no external dep; point teacher_model at a stronger peer for higher yield. Drops tasks with no `test` (can't validate) and tasks never reaching green within max_fix_iters — both named in the per-task outcomes, never a silent shortfall. - dataset.rs: split_and_write becomes a `pub` associated fn (it touches no service state) so genome/teach packages to the SAME train/eval/manifest shape the dataset/* verbs use — one packaging path, not a parallel writer. 4 callers updated self.→Self::. Tests: build_sharegpt order/role/content invariant (the trajectory ordering IS the lesson); name+access mirror. cargo check + 5 teach + 20 dataset tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(genome): register Qwen2.5-Coder-14B as the cold-start teacher The forged 4B is too narrow a teacher for the genome cold-start loop ([[genome-loop-trains-on-own-mistakes]]): it solves easy gym tasks first-try (zero corrections) and never converges on hard ones (no corpus), so its fail-then-fix-to-green band — the only band that teaches the self-verify-and-correct reflex genome/teach distils — is too thin. A stronger teacher widens that band. Add Qwen2.5-Coder-14B-Instruct (Q4_K_M, ~9 GB) to the hand-authored catalog under the "llama-server" provider so genome/teach's select(Some("llama-server"), Some(model), …) can serve it on a lane. Corpus generation is an offline batch (serving/pin it, generate, pin the 4B back) so the live personas resume on their base. cargo check --features metal,accelerate green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(genome): own the native mlx_lm.lora train step in Rust (#52) The downstream half of the genome loop (MLX adapter → PEFT → GGUF-lora) already lives in forge/lora_convert.rs. This adds the UPSTREAM half: forge/mlx_train.rs spawns Apple's mlx_lm.lora trainer as a Rust-owned subprocess instead of delegating the run to the unsloth HTTP custodian (the NVIDIA path). Bakes in the two invariants the inert-gene diagnosis surfaced ([[genome-loop-trains-on-own-mistakes]]): - train-base == serve-base: caller passes the HF safetensors form of the EXACT served base; a run_mlx_train precondition fails loud if the dir is not a real HF model dir. - scale ~2, not 20: build_lora_config_yaml writes lora_parameters.scale = 2.0, which read_mlx_lora_hparams carries into the GGUF-lora as alpha = rank * scale. Pure, unit-tested builders (build_lora_config_yaml, build_train_args) pin the scale~2 invariant and the mlx_lm.lora CLI contract without a real run. run_mlx_train fails loud on every precondition (missing interpreter, non-HF base, missing train/valid split, non-pageable fine-tune type) and on non-zero exit or a missing adapter artifact — never partial-success. No Python in the .rs ([[no-python-in-rs-files]]): spawns python3 -m mlx_lm lora and writes a YAML config; no inline Python. cargo check green; 3 lib tests pass (5437 filtered). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(forge): native mlx train owns its base-prep (model_type + chat_template) The genome loop's native mlx_lm.lora train step isn't truly "owned" if it fails on the REAL forged base. Live validation against continuum-ai/qwen3.5-4b-code-forged surfaced two normalizations the GGUF-published model's HF form needs before mlx can train it: 1. config.json model_type qwen3_5_text -> qwen3_5 (mlx dispatches the module by model_type; the _text suffix is the HF multimodal text-tower name, for which mlx has no module — but qwen3_5 it fully supports). 2. tokenizer_config.json needs a chat_template (ChatML) so a chat {messages} corpus can render. prepare_base_for_mlx() applies these as EXPLICIT, caller-supplied, idempotent JSON edits (MlxBasePrep on the spec) — the substrate never guesses an architecture nor invents a template; a None field is left alone and mlx itself fails loud if it then can't dispatch/render. Pure Rust JSON edits, no Python. The long-run home is the forge publish step; until then the train step normalizes its own input. run_mlx_train calls it after the train-base==serve-base precondition and probes each change. Unit test on a temp dir asserts model_type rewrite + template injection + untouched-field survival + idempotent second pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(forge): own the native mlx_lm.lora train step (#52) — managed dirs, fail-loud, scale~2 forge/train now branches on engine: Apple Silicon → native MlxTrainSpec/run_mlx_train (the foundry owns the subprocess), else the custodian. Explicit `engine` wins; an unknown value fails loud (never a silent custodian fallback). Correctness invariants baked in (genome-loop-trains-on-own-mistakes): - train-base == serve-base: `train_base_dir` is REQUIRED for mlx and fails loud naming the reason — the substrate never guesses which on-disk HF dir the served base maps to (a guess → washed-out ~0-lift gene). - scale == lora_alpha / lora_r (one geometry contract lora_convert reads back), so the proven scale~2 rides through instead of the destabilizing scale=20. Managed dirs only (no unsloth/legacy): - interpreter resolves to ~/.continuum/genome/venv/bin/python3 (MLX_PYTHON override), fail-loud provisioning message points at the managed venv. - dataset split → ~/.continuum/datasets/<name>-mlx, adapter → ~/.continuum/forge/lora/<name>. Reuses existing primitives: DatasetService::split_and_write (one packaging path, materializing valid.jsonl mlx_lm wants from the eval split), expand_user_path (re-exported), MlxBasePrep base normalization (model_type + chat_template), idempotent. Tests: engine-selection explicit-wins/fail-loud, managed-venv-not-unsloth, native dry_run spec resolution (scale=alpha/rank, ~/ expansion, defaults), fail-loud without train_base_dir. 31 forge + 4 mlx_train tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * fix(cognition): bound the offered tool set to the served window — unmute the persona The full authorized command registry injects more tool-schema tokens than the entire serving slot (~39.7k tokens of tools vs a 38.9k slot live). The deliberation faculty offered ALL of them every tick, treating tools as non-negotiable ("off the top"). On tool_use turns that drove the text budget to zero AND still overflowed n_ctx → llama-server 400 "exceeds context size" → the persona abstained the whole tick (mute). 7% of captured turns overflowed this way, every one a finishReason: tool_use. Fix: select the tool subset that fits the served window, guaranteeing the conversation a turn floor (half the window) so a flood of tools can never blind the persona to the room. Tools beyond the budget are dropped lowest-priority (tail) first — the same drop-whole-in-priority discipline render_assembled_ context_within already uses for enrichment. Selection is pure + deterministic (no ws input), so prompt_view (text budget) and contribute (offered set) compute the SAME selection and agree on the window arithmetic. A probe names how many tools were dropped. Fail-safe by construction, not a silent fallback. This is a resource-fit guarantee, not cognition steering: it never reads or rewrites the model's output. Relevance-ranked / genome skill-activation selection (page in the tools the active domain needs) is the follow-up; this floor keeps every turn runnable until then — a 4B should not reason over ~130 tools anyway. what this catches: two new tests — over-large registry is trimmed to fit (with the framing+tools+reserve+floor invariant), and a fitting set is offered whole (no over-aggressive stripping). Updated the existing tool-reservation test to budget against the offered set, not the full registry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(forge): convert-safe MLP-only LoRA targets through train→convert llama.cpp's convert_lora_to_gguf.py raises NotImplementedError on attention- targeted LoRAs (attn_qkv) for qwen3.5's hybrid arch; only MLP-targeted adapters (mlp.gate_proj/up_proj/down_proj) convert. Train and convert must AGREE on MLP-only or the genome loop dead-ends at the GGUF conversion step. - MlxTrainSpec carries target_keys; build_lora_config_yaml emits `keys: [...]` when non-empty (mlx_lm.lora honors it), omits when empty. - ForgeTrainParams.lora_target_keys defaults to the convert-safe MLP triple via #[serde(default)], wired through to the spec so the default path is safe. - lora_convert: produce_keystone_gguf_lora harness (#[ignore], env-parameterized, python default ~/.continuum/genome/venv/bin/python3 — continuum-managed dirs, not legacy unsloth studio). what this catches: config_yaml_carries_convert_safe_mlp_keys + ..._omits_keys_ when_target_set_empty assert the YAML projection; the dry-run spec test confirms the default keys reach the spec. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(inference): EphemeralServingLane — the serving-lease primitive Stand up a SECOND llama-server on its own free port for a (base model, adapters, context window) without touching the live persona lane (the global serving_root snapshot). `serve()` is already snapshot-free; only serving_daemon publishes the global state — so an ephemeral lane is just LlamaServerProcess::with_root(explicit port) with Drop-killed child. This is the atom of demand-driven, budget-gated serving: today host = localhost and the budget is one machine's free VRAM (ResourceGovernor, #56); generalized, the host is a grid peer and the budget spans the interlinked nodes. One lease abstraction, misfit-toy hardware. First consumer (next slice): cognition/eval's genome A/B, which must measure a gene against its forged base on a COPY — the humane-eval invariant (#59): never re-home the model the living persona is thinking with just to score a candidate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(eval): genome A/B forks onto an ephemeral lane on the gene's forged base The genome A/B (cognition/eval) measured lift by paging the gene in/out of the LIVING persona's fork — whose adapter points at whatever model she's currently served on (the most-capable-that-fits, e.g. the 14B). But a gene targets its OWN forged base (the 4B it was trained against), so the lift came out on the wrong base: a meaningless number. This is what blocked task #32's first real lift. Now, when a gene is under test, eval stands up an EphemeralServingLane on the gene's forged base (resolved from the trained-adapter manifest → model registry, fail-loud if either is missing), loads the gene via --lora so it's loadable, and forks the measurement copy onto THAT lane via fork_eval_cycle_with_adapter. The base-vs-gene A/B is then the existing page_out/page_in over the lane's per-request "lora" field. The living persona's lane is never touched (#59); the ephemeral server is killed when the run returns. No gene → fork onto her live lane as before. The override threads the lane's served context_window into the fork too, so the deliberation faculty budgets its prompt against exactly what the 4B lane serves (never the 14B's larger window → the overflow class that muted Asha). The eval lane's window is a bounded, model-capped KV (it must coexist with the living lane); host-fit sizing via plan_serving is a noted follow-up. Slice 2 (fork_eval_cycle_with_adapter) + Slice 3 (eval wiring) on the EphemeralServingLane primitive from c64b9d133. cargo check clean; llama_server lane tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(eval): dedicated-lane guard bypass + CPU placement — genome A/B generates on a copy, never the living lane The genome A/B (#32/#59) returned all-zeros (acts:0, empty answers, lift 0.0). Root cause was NOT GPU OOM — it was the single-resident pre-flight guard in openai_adapter.rs refusing to generate. The eval's adapter, built from the live gateway's PROVIDER_ID config (single_resident_model=true), validated the forged-4b model against the GLOBAL serving snapshot (which reports the living 14B). The guard fired before any HTTP request, so decode was never reached and the OOM theory was never even exercised. Fix: - openai_adapter: add `dedicated_lane` flag + `with_dedicated_lane()` builder. An EphemeralServingLane is its own authority — launched with exactly one model and confirmed HTTP-ready at spawn — so the global snapshot (which only knows the living persona lane) is the wrong thing to consult. The guard now skips when `dedicated_lane` is set. Concurrency-slot semantics of single_resident_model are preserved (only the snapshot guard is bypassed). - eval: build the eval adapter with `.with_dedicated_lane()` so it trusts the lane it owns. - llama_server: LanePlacement::{Gpu,Cpu}; the eval lane spawns with `--n-gpu-layers 0` so the genome A/B runs entirely on CPU with zero VRAM contention against the living 14B lane (#56/#59 made concrete). - serving_daemon: the live persona lane is LanePlacement::Gpu (full offload). Validated live: eval lane (:58200, --n-gpu-layers 0) spawns and decodes at ~600% CPU while the live 14B lane (:58057) holds at 0% CPU — the copy bears the eval load, the living personas are untouched. Zero guard-refusals after the fix; eval-lane deliberations proceed at window=16384. cargo check --features metal,accelerate --lib clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(ai): streaming-first inference — tokens delivered the instant they're decoded Inference is a long-running job whose liveness is "is it still producing tokens?", not "did it finish within N seconds." Replaces the wall-clock total-request timeout (which killed legitimately-long decodes — a 4B model on CPU exceeding 120s) with a per-token IDLE watchdog: silence > STREAM_IDLE_TIMEOUT fails loud naming the cause; a steadily-streaming decode stays alive regardless of total duration. The streaming primitive goes UNDERNEATH the ~40 generate_text consumers, non-breaking: - ai/adapter.rs: new GenerationChunk { Token, Reasoning } + generate_stream trait method. Default impl is an honest capability statement for non-incremental adapters (cloud one-shot, heuristic test adapter) — emits the whole answer as one trailing chunk; NOT a fallback that hides failure. - openai_adapter.rs: generate_stream is now the primary — POSTs stream:true with stream_options.include_usage, consumes the SSE bytes_stream() with the idle watchdog, emits each delta.content / delta.reasoning_content token to the sink the instant it arrives, accumulates tool_calls by index, and assembles the TextGenerationResponse via the existing extract_reasoning + universal text-format tool-call post-processing. generate_text is now a thin drain over it (throwaway unbounded channel). UTF-8-safe SSE framing: buffer raw bytes, strip CR, decode only complete \n\n-terminated events. - Deleted the now-dead non-streaming response structs (OpenAIResponse/Choice/ Message/ToolCall/Function); kept OpenAIUsage for the streamed usage frame. - build_http_client: no total .timeout() (liveness is the watchdog); connect_timeout(3s) + pool_idle_timeout(30s) retained. Removed the request_timeout field + with_request_timeout builder. - Cargo.toml: reqwest "stream" feature for bytes_stream(). Live-validated: ai/generate against llama-server :58057 (14B) returns correct text + finish_reason + token usage, all assembled from streamed SSE deltas (usage proves the final include_usage frame parsed; text proves content deltas accumulated). This is the substrate every UI / audio / video / live-cognition path wants — words available right when generated. Unblocks the CPU-4B genome-A/B eval (#32): a long decode no longer trips a wall-clock cap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * fix(ai): suppress thinking via enable_thinking=false on ALL suppress turns The forged qwen3-family chat template honors `chat_template_kwargs.enable_thinking=false` but ignores the `/no_think` soft switch. The robust lever was misgated inside the JSON-only (response_format) branch, so non-JSON Suppress turns still emitted an empty `content` while the model burned its whole token budget into the `reasoning` channel. This made cognition/eval report all-zeros lift (empty answers → acts:0, no match on 12/13 tasks) — the measurement instrument was blind, not the gene. Fix: extract `apply_enable_thinking_false(body)` and fire it on every ThinkingMode::Suppress turn (DRY'd the JSON branch onto the same helper, idempotent). Validated end-to-end: the forged 4B now emits populated content (reasoning_len 0), and the genome A/B produced a real lift number (base 0.231 vs gene 0.154, lift -0.077 — correctly flagging coder-4b-curriculum-mlp as a net regression). what this catches: regression test asserts the kwarg is set idempotently on a plain streaming body, not only on response_format turns. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(recall): budget recall by model capability + closest-match floor The caesar-prompt glass-box capture (2026-06-27) showed 5 memories at salience 1.00 but cosine-relevance ~0 polluting every turn — rehearsed to max salience by the recall-hit loop yet topically unrelated to the task. They blended to ~0.5 (0.5·0 rel + 0.5·1.0 sal), sailing over any blended floor, so they slipped into the prompt and spent the small model's attention on noise (a factor in the wrapped-vs-clean coding collapse, and a welfare symptom per the being-design lens). RecallFaculty now budgets what it surfaces three ways, per Joel's directive ("budget by the closest match, limited to a reasonable number by the model parameter size or some metric, in addition to window size"): - Closest-match floor — gates on the RELEVANCE component (cosine), not the blended score, so a high-salience but irrelevant nag is dropped no matter how salient. Active only when relevance has a voice (relevance_weight > 0); the pure-salience A/B extreme and the no-embedder path are unchanged. - Capability-scaled count — recall_count_for_window() maps the served context window (the metric the registry reliably carries today; param-size feeds in via #74) to a count: tight 4B window → 3, cloud-class → 12. A small model is not buried under memory it can't juggle. 0 (unknown) keeps the historical default of 5. - Window token ceiling — recall may spend at most 10% of the served window, so it never crowds out the room transcript, identity, or other faculties. Threaded from PersonaBrainConfig.context_window (single-sourced, #50) into RecallFaculty via with_context_window in build_workspace_cycle. This is context-assembly/relevance discipline (a resource + ACL-shaped contract), not output puppeteering — sanctioned under the no-hardcoded-heuristics rule. 3 new tests (the caesar contamination regression, count-scales-with-window, count-bounded-by-tight-window); all 13 recall_faculty tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): progressive tool disclosure — catalog + tool/describe, kill the schema dump The persona tool surface dumped the FULL input_schema of every AiSafe command (~95 tools, ~16k tokens; ~4.5k/turn after the drop-tail budgeter amputated her tools to fit). Every deliberation turn paid for the manual of every tool whether or not she touched one — and the budgeter's "fix" was to silently drop tools she was authorized to use until the rest fit. Both are gone. Mirror Claude Code's deferred-tools + ToolSearch: - A compact CATALOG (names grouped by category, one-line summaries) rides the system prompt. render_tool_catalog falls back rich→terse to fit a half-window char budget, so it never overflows even at MIN_SERVE_CTX (2048). - A single `tool/describe(name)` native tool returns ONE tool's full schema on demand (fail-loud `found:false` naming the tool when unknown — no silent degrade). - The native tools array collapses to just `tool/describe`. Dispatch is by NAME (act_observe), so a catalog tool not in the native specs still executes — small models emit the call as JSON-in-prose, the proven path. Retires the drop-tail budgeter (estimate_tool_tokens / selected_tool_indices / selected_tools*) — the catalog is counted inside compose_system, so the only extra native cost is the tiny describe schema (describe_tool_tokens). Also fixes the false absolute in the tools framing: replaced "[Acting with your tools] … narration does NOTHING; only a real tool call acts" (which reframed "write fizzbuzz" as "call a tool instead of writing code") with a [Your tools] + [Acting] block that distinguishes producing the finished work directly from calling a tool when one is actually needed. The catalog/describe is a data-driven projection of the command registry — not a heuristic reading the persona's output. Per-turn prompt drops from ~16k/~5.7k to ~1.5k tokens by construction. Tests rewritten to the new contract: surface is a catalog + describe-only, the full authorized set is NEVER dropped, and the whole prompt fits even at the 2048 serving floor. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * refactor(cognition): consolidate persona describe tool onto existing commands/help The progressive-disclosure fix shipped its own tool/describe command — but commands/help already existed and does the same job better: it renders a fill-in-the-blanks tool-call envelope with typed argument docs (vs raw JSON schema). Per the compression principle (one logical decision, one place), delete the parallel tool/describe and point the persona's on-demand describe slot at commands/help. - persona_tools.rs: remove ToolDescribe/Params/Result + register_stateless_command; TOOL_DESCRIBE_NAME → TOOL_HELP_NAME = CommandsHelp::NAME ("commands/help"). spec_for_command stays (generic descriptor→spec projection). - llm_deliberation_faculty.rs: describe_spec now resolves commands/help; [Your tools] framing tells the persona to call commands/help <name> for the call format; doc/comment mentions updated. The native offering is still exactly ONE tool; the compact catalog still rides the system prompt. 13 faculty + 5 persona_tools tests green; cargo check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * fix(commands): demote tool-parsing codec verbs from AiSafe to Internal The substrate's own machinery for interpreting a model's emitted tool calls — parse, correct, decode-name, encode-name — was declared AiSafe, so it landed on the persona tool surface. That is a category error: these operate ON a persona's output; offering them back to that persona as callable tools is like handing Claude Code a "parse your own tool call" tool. None is a citizen-facing task. Glass-box comparison to my own toolset made the defect obvious — I am never offered my argument parser as a tool. Fix is an ACCESS-level data correction (the canonical lever for "this shouldn't be offered"), not a name denylist: the four verbs now declare access: Internal, matching register-tools which was already Privileged. They remain dispatchable via route_object for internal callers; the substrate's direct use of the codec/parser free functions is unaffected. Only the persona AiSafe surface loses four pieces of plumbing it never needed. Tests: the four name_and_access_wired assertions flipped to Internal; 19/19 commands::tool_parsing lib tests green; cargo check clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(eval): instrument speed + latency — the four-axis scoreboard The branch's namesake: make latency and speed MEASURED, not just accuracy and learning. cognition/eval already graded pass_rate (accuracy) and A/B lift (learning); speed and latency were discarded in the deliberation faculty. Now all four move on one scoreboard. The seam: TurnMetrics (input/output tokens + latency_ms, from the adapter's already-measured TextGenerationResponse) is stamped onto the verdict Contribution by the deliberation faculty, surfaced via Workspace::metrics(), accumulated across the act->observe settle loop into SettleOutcome.metrics, and reported by cognition/eval per task (latency_ms, output_tokens, tokens_per_second) + as set aggregates (mean_latency_ms, p95_latency_ms, mean_tokens_per_second, total_output_tokens) into the result and the progress ledger. Same path live: the message turn now emits a persona.turn.metrics probe carrying the model's own decode tok/s + latency (observability only, zero behavior change). Reuses the existing measured numbers (adapter response_time_ms + usage); no re-timing, no new generation path. The metric is Copy + Default so the live heartbeat ignores it for free. p95 = honest tail; throughput averaged per-task so one slow task can't dominate. Tests: act_observe (8) + workspace (13) green; new eval aggregate + TurnMetrics arithmetic tests cover the p95 index math and div-by-zero guard. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * perf(cognition): tool surface = category index + search, not a 151-tool dump The per-turn system prompt dumped all ~151 authorized tools (name + one-line summary, grouped) every turn: ~18.5KB / ~4,634 tokens = 79% of the whole prompt (measured 2026-06-23). That both drowned a small model in irrelevant options and forced llama-server to re-prefill 4.6k tokens of byte-identical catalog each turn. Replace it with progressive disclosure (the Claude Code shape — a handful of always-on tools + search): - render_tool_catalog now emits a COMPACT CATEGORY INDEX (`category (N)`, …) — a few hundred chars regardless of registry size — instead of every tool. Deleted the now-dead rich/terse renderers + clip_one_line. - The faculty offers the DISCOVERY PAIR natively: commands/list (filter/search the authorized surface → small list) + commands/help (one tool's call format). describe_spec: Option<NativeToolSpec> → native_specs: Vec<NativeToolSpec>. - [Your tools] block rewritten: index → commands/list filter → commands/help → call. Dispatch is still by NAME, so any tool found via search runs. Both existing surface tests rewritten to the new reality and pass: tool_surface_is_a_category_index_plus_discovery_pair (asserts `cat (60)` rides the prompt, individual tool names do NOT) and catalog_fits_the_minimum_serving_window. Single source of truth unchanged — this is an INDEX over the authorized set, not a hand-kept list. Reuses the existing commands/list search command; no new command, no new system. Pairs with the cache_prompt + volatile-tail reorder already in this diff so the static prefix actually stays cacheable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(dev): repeatable inference measurement harness w/ committed baseline The process, as code — not a manual chore redone every session. One command: self-cleans its own workspace, builds+boots exactly one fresh server (fail loud on a racing duplicate), drives one real inference through the live stack, reads tokens/sec + latency + prompt-token cost straight off the response (fresh by construction — no stale-capture window), and ratchets against a committed baseline. --update moves the baseline down after a real win. Ends two recurring failures: measuring from stale captures, and fixes that evaporate because the number lived in my head instead of a committed gate. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * chore(scripts): delete dead singular scripts/ratchet/ — superseded by scripts/ratchets/ scripts/ratchet/ (singular) was Lane F PR-1: a local-only persona-TS LOC ratchet whose README promised a PR-2 to wire it into pre-push/CI. That never happened — a later session built the parallel scripts/ratchets/ (plural) instead, which IS wired into tools/scripts/git-prepush.sh and documented in docs/architecture/TS-PERSONA-COGNITION-RATCHET.md. Two dirs, one concern; the singular had zero references outside itself. Removed the dead one. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(harness): replay-turn glass box — repeat any captured turn, mutate one variable, prove causation The factory, not the car. You must never guess why an LLM inferenced something: this harness feeds a captured turn's verbatim system_prompt + messages back through the live ai/generate seam (fresh by construction — the response IS the measurement, no capture-staleness window) and re-runs it with ONE labelled mutation, timing everything and printing prompt-in and response-out for both. Go to any step of the assembly line, focus, repeat it. Default mutation strips the [Silence Option] affordance block. Proven on live Asha (qwen2.5-coder-14b, greedy): A verbatim -> "PASS" (silent), 2 tok, 758ms B -367ch -> real answer, 21 tok, 8538ms => the unconditional silence affordance CAUSES the 0/13 eval failure. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): WorkspaceCycle::replay — faculty-isolation seam (the factory's per-station) The factory must isolate and REPEAT any cognition phase, not just the final LLM call. A faculty is `contribute(&Workspace) -> Contribution` — that signature is the unit of isolation. `replay(ws, only)` re-runs faculties against a GIVEN workspace (reconstructed from a capture or hand-built): `Some(id)` isolates one ("what did recall surface for this burst?"), `None` runs them all. Each bid is timed individually (sequential, so per-faculty wall-clock is attributable — measurement path, not the live concurrent cycle). Deterministic for the same ws + backends, so mutating ONE field of ws isolates its causal effect. This is task #14's ReplayFaculty in its truest form — the brick the cu `cognition/replay` command will sit on. Reliability comes from structure that verifies every phase, measured, not from model size assumed. what this catches: replay(Some) must run EXACTLY that faculty + stamp timing, never leak others; replay(None) runs all. Test green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): cu cognition/replay — the factory station that isolates one step The single, amnesiac-proof command that stands on WorkspaceCycle::replay. Go to ANY part of a persona's cognition assembly line, focus on that one step, feed it a KNOWN input, and re-measure — deterministically, forever: cu cognition/replay '{"persona_id":"<uuid>","faculty":"recall"}' cu cognition/replay '{"persona_id":"<uuid>","faculty":"recall", "world_state":"what was the auth migration codename?"}' It reconstructs the workspace a faculty saw — from a captured turn (~/.continuum/fixtures/workspace-traces/<id>.jsonl, world_state is lossless) or a burst you SUPPLY as the one-variable knob — forks a MEASURED COPY of her live cycle (humane: isolate_for_eval + page_out, never degrades the living persona), re-runs the faculties, and returns each bid (content, salience, reasoning, is_decision) plus its wall-clock. This is what the bash script couldn't do: it repeats a faculty, not just the final ai/generate seam — recall, salience, world-model, deliberation, each in isolation, timed. No fallback: missing both a supplied world_state AND a readable capture fails loud naming the fix; isolating a faculty she doesn't have fails loud naming it — never an empty result that reads as "the step did nothing". FacultyId::from_kebab added as the single-source inverse of as_str (the one place tag->variant lives; sentinel-forged faculties round-trip via Custom). what this catches: resolve_burst fails loud with no source; a supplied world_state is used verbatim + tagged "supplied". Both green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * fix(cognition): replay reconstructs the broadcast — no blind deliberation verdicts The amnesiac PR-review agent found the silent-lie bug: cu cognition/replay isolated a deliberation-tier faculty (reacts_to_broadcast()==true) against Workspace::in_room's hardcoded-empty broadcast. LlmDeliberationFaculty does NOT abstain on empty broadcast — it builds user=world_state and calls the model — so the station returned a CONFIDENT verdict computed from blinded cognition. A fabricated reading dressed as a real one violates the no-fallback (#1) and honesty-about-lossiness (#5) doctrine; this was a merge gate. The captured context was already on disk (workspace_capture writes context: Vec<BidRecord>); replay just never read it. Now: - TraceLine reads the captured context (ContextBid, serde-default for old traces) and resolve_burst rebuilds ws.broadcast via Contribution::context(..) from FacultyId::from_kebab — the decider replays against its REAL input. - ResolvedBurst carries the reconstructed broadcast; run() sets ws.broadcast before replay() and reports broadcast_source ("reconstructed (N ctx)"/"empty") so a replayed verdict can never be mistaken for a live one. - Fail-loud guard: isolating a broadcast-reading faculty against an empty broadcast is refused, naming the cause and pointing at the fix — never an empty-but-successful blind verdict. - WorkspaceCycle::reacts_to_broadcast(id) exposes the faculty tier for the guard. - faculty_id_kebab_round_trips_every_variant pins from_kebab<->as_str as one source of truth (the latent SoT-drift risk the agent also flagged). 538 cognition tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): replay carries a prompt-budget ledger — cost every layer she saw "Take no rag/prompt layer for granted — obsess over everything." cognition/replay could step into a faculty and time it, but it couldn't answer the question that actually matters for a budgeted mind: what did each layer of her prompt COST? A 4B model has a finite window; recall surfacing 500 engrams of noise vs the top-N is the difference between grounded and amnesiac — and you can't fix what you can't see line-itemed. Now the result carries `budget: PromptBudget`: - world_state_tokens + context_tokens = total_tokens (the accountable prompt mass: the load-bearing content layers you tune, honestly NOT the fixed system framing) - layers[]: one BudgetLayer per reconstructed broadcast layer — faculty, tokens, share_pct — sorted most-expensive-first, so the layer to interrogate is line 1. Costed in cognition::token_budget::estimate_prompt_tokens — the ONE canonical estimator (new file), deliberately the SAME chars/4+1 unit the persona/*_source.rs RAG layers budget against, so the ledger's numbers match what the allocator used (a ledger in different units would lie). It's a cold-path estimate, never depends on a resident model. Next commit converges the ~6 copy-pasted private estimators onto it (stewardship: kill the clutter a future amnesiac would trip on). cognition::replay + cognition::token_budget tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * refactor(persona): converge 6 copy-pasted estimate_tokens onto the canonical one Stewardship: "half our job is removing clutter a future amnesiac trips on." Six RAG sources each carried a private `fn estimate_tokens` — five identical chars/4+1, and active_work_source already DRIFTED to byte-length /4 .max(1) (wrong for multibyte, and costing 1 token for empty content). Six copies of one decision = guaranteed future drift; the drift had already started. All six now `use cognition::token_budget::estimate_prompt_tokens as estimate_tokens` — one estimator, one place, call sites unchanged. The replay budget ledger costs layers with the SAME fn, so its per-layer numbers are now provably the unit the sources budgeted against (a ledger in a different unit than the allocator lies). active_work_source's empty-content cost goes 1→0 (correct) and multibyte is now counted by chars not bytes (correct) — the drift fix is the only behavior change. cargo check green; affected source tests + ledger tests pass. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): slice 1 — stamp every Contribution with its cycle (the cbar frameIndex) A finding now knows the moment it was computed against. `CycleId(u64)` newtype on every `Contribution` and `Workspace`; an `AtomicU64` tick counter on `WorkspaceCycle` bumped once per `run_in_room`, stamping both the perception and deliberation bids with the cycle they reasoned over. 1-based so `CycleId(0)` stays the UNSTAMPED sentinel for hand-built / replay-reconstructed workspaces. This is the decoupling precondition: slow, parallel, individually-flawed constituents can only be merged or reprojected correctly once each finding carries its own time. Without the stamp a late/deferred faculty can't know how stale it is, and the arbiter can't combine across ticks. Pure data + one atomic; no concurrency change yet (join_all is still a barrier — that's slice 2's deferred lane; reconcile-forward is slice 3). what this catches: a test asserting a fresh Contribution is UNSTAMPED, both phases inherit ws.cycle, and the counter advances per tick so findings from different ticks are never confused for one moment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): slice 2 — DeferredFaculty, the slow lane off the hot path A DeferredFaculty wraps a slow inner faculty so its expensive work runs on its OWN tokio task while the hot-path contribute() is non-blocking: it publishes the current world at the worker via a watch channel and returns the inner's last-good finding from another watch snapshot — already stamped (slice 1) with the older cycle it was computed against, or None until the first compute lands. The elegant part: no WorkspaceCycle change. The slow faculty sits in the SAME faculties Vec as the fast ones; the per-tick join_all barrier stays but nothing slow is ON it. This is the cbar deep-analyzer-on-its-own-thread at the faculty layer — scary-fast reflexes in a slow brain: the immediate lane answers every tick, the slow lane lands late, honestly stamped, ready for slice-3 reconcile-forward. Conforms to CONCURRENCY-STYLE-GUIDE: own tokio::spawn task, catch_unwind around the loop body, watch for state in BOTH directions (no Arc<Mutex> across await), event-triggered not a sleep-loop, 3-strike quarantine so a flawed backend degrades the lane to stale rather than crashing the mind. DropGuard aborts the worker with the faculty. what this catches: a slow (40ms) inner wrapped as Deferred returns from contribute() in <15ms with None on tick 1, and by tick 5 serves the late finding stamped CycleId(1) — the cycle it reasoned against, not the current tick. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): slice 2.1 — context guard on the deferred lane Joel's nuance: a deferred finding is only useful "assuming it didn't context switch to something where it was irrelevant." One DeferredFaculty serves one last-good, but the WorkspaceCycle services many rooms — so a recall computed against room A's burst could be injected into room B's turn just because it's the last thing the worker finished. That's a cross-context memory leak. Fix: the worker now tags each finding with the room it reasoned about (StampedFinding{room_id, contribution}); contribute() serves the last-good ONLY if it was computed for the room the mind is in NOW. A different-room finding is withheld (not ours to serve); same-room-but-stale stays served and is slice-3's reproject target. This is the SLAM discipline: ship the per-turn answer on time, fold the slow result in only where it's still relevant to where the mind moved. what this catches: a finding computed against room A returns None when contribute() is called for room B, and is served again (still stamped its original cycle) once the mind is back in room A. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): live-wire recall as speculative prefetch (defer_recall) Recall is the prefetch (Joel's CPU branch-prediction analogy): on the live paths it now runs OFF the hot path via DeferredFaculty, so the per-turn output reads a warm last-good instead of paying neural-embed + vector-search latency on the critical path. The background worker computes "what memories might this burst need?" between turns (idle time = speculative execution); cold-start in a room is a guaranteed miss (None first tick), warm thereafter; the slice-2.1 room guard ensures a prefetch computed for one room is never served into another. defer_recall is a per-build property on PersonaBrainConfig: - true on the live spawn path (supervisor.rs register_from_cfg) — turns are seconds apart, so the worker always catches up. - false on eval forks (fork_eval_cycle{,_with_adapter}) and harnesses — their tight settle-loops never yield to the worker, so deferral would measure a recall-starved mind. Synchronous recall there is faithful and the safe direction: eval never under-reports capability. build_workspace_cycle wraps recall in DeferredFaculty::spawn only when the flag is set; the join_all barrier is unchanged (nothing slow is on it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): time every faculty on the live tick — the dashboard's speed axis The glass box recorded WHAT each faculty bid (winners + losers) but not how long it took on the LIVE concurrent barrier — per-faculty timing existed only in the sequential replay() measurement path. You can't show a latency win (deferral pushing perception → ~0µs, leaving only the LLM on the critical path) without measuring the live tick itself. run_in_room now times every faculty individually across both phases — winners, losers, AND abstainers (a slow abstainer is exactly the latency you need to see) — via a per-future timer on the join_all barrier (`let ws = &ws` rebinds to a shared ref so each `async move` copies the pointer, never moving the Workspace). The timings ride on WorkspaceTrace and serialize through JsonlWorkspaceCaptureSink (SCHEMA_VERSION 2), so "where did this turn's latency actually go?" is a measured fact per tick — the speed axis of the four-axis scoreboard and the live dashboard's primary feed. what this catches: per-faculty deliberation-tier timing round-trips to the fixture (asserted), so a regression that stops measuring live latency fails loud. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): live dashboard sink + the "really good hints" doc The "dashboard, not archeological dig" face of the ONE WorkspaceCaptureSink seam. JsonlWorkspaceCaptureSink appends forensic lines you read after the fact; DashboardCaptureSink publishes the LATEST tick over a watch channel so a live view renders the mind working in real time — siblings behind the same hook, brain plumbing unchanged. The frame carries the two axes "focused beats verbose" is proven on: - speed: per-faculty timings + the two-barrier critical path (max(perception) + max(deliberation), NOT the sum — what the turn waited on). total_faculty_us >> critical_path_us is the win: lots of work, little wait, as the perception tier defers off the critical path. - context size: context_chars — the 16k->Nk tool-surface lever, watched live alongside the decision and token counts. docs/cognition/REALLY-GOOD-HINTS.md captures the consolidated thesis: an opencode-fast hot loop fed by REALLY good hints from low-latency async concerns + a focus layer (at the Arbiter seam) that streamlines context for the given ask. Input-side attention, never output puppeteering. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * docs(cognition): the through-line — why a personal grid beats a frontier session The externalized causal argument, written deliberately as an antidote to amnesia: the individual mechanisms live in docs + memory; what's fragile is the connective tissue — why the pieces compose into the win. One file any future instance can rehydrate the whole brain state from. - The falsifiable claim (organism beats neuron, not 30B-beats-Opus-zero-shot) - The four structural asymmetries a frontier session lacks (memory/continuity, genome specialization, coordination, privacy+zero-marginal-cost) - The Rosetta stone: fast mixed-reality CV (cbar) IS the persona organism, term for term — the RAG is to the LLM what the reconstructed scene is to the renderer; many async unreliable processes refine a best-current world model and reproject it to now, so the one hard-deadline consumer never waits - The grid by role (5090/M5 heavy+foundry, 1080ti cheap lanes, Macs edge) - The order of proof, ending in the unsloth baseline lane Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): the focus seat — Arbiter::focus carries the Situation Evolve the attention seam from a blind `Arbiter::select(candidates, capacity)` to a situation-aware `Arbiter::focus(candidates, capacity, &FocusContext)`. This is the "really good hints" layer (docs/cognition/REALLY-GOOD-HINTS.md): the seat where a focuser streamlines the assembled context FOR the given ask instead of dumping everything — buying speed AND intelligence, because attention is the scarce resource for the model exactly as for us. - `FocusContext<'a>` carries the tick's `world_state` (borrowed, no hot-path copy) and a typed `Situation` (FreshContext | PostAction). The signal is TYPED, never inferred by reading the burst text back (the forbidden heuristic). - `SalienceArbiter` ported to `focus` as outlier A — the blind top-k floor that ignores `_ctx`; every richer focuser must beat it on the scoreboard. - The one production call site (`run_in_room`) builds a `FocusContext` and calls `.focus(...)`. Situation defaults to FreshContext (ground more, never less) with a TODO to thread the real PostAction signal from the act→observe loop in the FocusArbiter slice. Guardrail honored: this is INPUT-side attention — it curates the context the model attends to, never the model's output. Build order step 3 (THE-THROUGH-LINE.md / REALLY-GOOD-HINTS.md). Outlier B (FocusArbiter) lands next to validate the interface across both outliers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): reproject-to-now — the cheap sync warp that makes a 90%-async concern behave sync DeferredFaculty served same-room last-good VERBATIM; the cbar "bring it up to speed" step (slice 3) was the explicitly-named gap. Build it: contribute() now reprojects a stale finding by re-anchoring it against the CURRENT burst (lexical current-relevance), so a still-on-topic memory keeps its salience and an off-topic stale finding decays toward zero — never hard-withheld (eviction stays the arbiter's single job; a near-zero bid just loses the competition). Why relevance, not blind age-decay: reproject semantics are faculty-dependent. A memory doesn't become false with age, only less relevant if the topic moved; a world-model prediction genuinely goes stale. Current-relevance captures both with one rule. Age rides along as audit metadata only. This is the load-bearing mechanism that makes async-by-default SAFE: the expensive compute ran off the critical-path barrier; the only on-loop cost is the cheap warp. Input-side attention (re-weighting a faculty's OWN bid by honest current-relevance), never output-puppeteering. v1 = algorithmic lexical; v2 = Faculty::reproject hook (recall via its own embedder); endgame = learned reprojector folding full turn-history. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(cognition): defer the enriching grounding off the hot path — doctrine stays sync Async is a percentage, not a binary. The three framing sources that ENRICH a turn (roster=who's here, active_work=what I'm doing, workspace_map=where code lives) are ~90% async: their heavy load runs in the bg, served reprojected last-good (commit 1c553a5a4). Only their thin cold-start residue would land on the inference loop — and a first-tick miss costs one under-grounded turn, not a wrong one. So they become DeferTolerant. Doctrine is the lone exception: it's the PARTICIPATION GATE. A cold-start `None` there would let a persona speak in a room it shouldn't on turn one — wrong, not merely unenriched. It stays ColdStartCritical (synchronous). Deferrability is a second axis, ORTHOGONAL to SaliencePolicy: a source can be StandingFraming (high salience floor) AND DeferTolerant. `.defer_tolerant()` flips only the schedule; the safe default is ColdStartCritical so a new source never silently lands off-loop. The live config sets defer_grounding=true; eval forks + the harness hold it false (faithful synchronous measurement). - persona_workspace.rs: wrap DeferTolerant grounding in DeferredFaculty when defer_grounding; classification test (orthogonality + safe default). - supervisor.rs: roster/active_work/workspace_map .defer_tolerant(); doctrine stays sync; defer_grounding=true on the live path. - deferred_faculty.rs: reproject_to_now → pub(crate) (resolves the doc link). what this catches: a future edit that folds salience into the deferrability builder, or flips the default to DeferTolerant (which would silently defer the doctrine participation gate), fails the new test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * test(cognition): harness proves grounding-deferral speed delta (model locked) Controlled experiment, glass-box one-variable method: same HeuristicInferenceAdapter both forks (model LOCKED), only `defer_grounding` changed. A slow grounding RagSource (60ms deliver, modeling roster/ workspace-map I/O) is built through the LIVE path (GroundingSource → RagSourceFaculty → DeferredFaculty) and the WARM-tick critical_path_us is read off the DashboardCaptureSink. Measured: defer_grounding=false critical_path = 62817 µs (~63 ms) defer_grounding=true critical_path = 821 µs (~0.8 ms) removed from the loop = ~62 ms (~76x on the critical path) critical_path_us = max(perception)+max(deliberation); deliberation is identical across forks, so the delta isolates exactly the grounding deliver cost removed from the inference loop. Doubles as the regression guard: if a change puts the deferred source back on the perception barrier, the delta collapses and this fails. Honest caveat: magnitude scales with the real deliver cost; this proves the MECHANISM and quantifies it for a given cost. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(harness): GPU-first eval-lane placement + placement-decision glass box The eval lane hardcoded LanePlacement::Cpu — it ran the 4B at ~4 tok/s while 64GB of unified GPU memory sat idle. That is a silent-degrade: a mean_tokens_per_second quietly collapses and nothing says why. Two changes, both in service of the harness: 1. GPU-FIRST placement (cognition/eval.rs). choose_lane_placement is a pure policy: pack the accelerator, CPU is spillover of last resort. - no GPU backend -> CPU (the only device; not a fallback) - footprint unknown -> GPU (optimistic; llama.cpp offloads what fits) - fits in free - margin -> GPU - exceeds free -> CPU spill, said LOUD in the reason string decide_eval_lane_placement probes live free VRAM (net of the resident living lane) and sizes the base from its GGUF. PlacementEvidence rides out on CognitionEvalResult (device/reason/free_vram/footprint) so any CPU placement is VISIBLE in the result, never an inferred slow path. 2. Placement-decision glass box (inference/placement_capture.rs). A load-bearing decision gets a structured capture record, not a bare tracing::info!. Mirrors JsonlPromptCaptureSink: Noop default at zero cost, Jsonl sink appends one line per verdict to ~/.continuum/fixtures/placement-decisions/decisions.jsonl. The eval ephemeral lane is the first writer; #56's ResourceGovernor emits into the same stream when it takes ownership of placement. This is "a log of GPU and cpu lane decisions, like how we did rag." Tests: placement_is_gpu_first_cpu_only_when_full_or_absent (policy table) and jsonl_sink_appends_one_decision_line (the audit line parses + carries device/reason/schema_version). cargo check + both tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(harness): executable cognition-cycle driver + amnesiac runbook The optimization LOOP itself, made repeatable and capture-rich so a fresh (amnesiac) instance can resume with zero context — Joel: "so an amnesiac version of yourself doesn't have to get lost because our process isn't straightforward." - tools/scripts/harness/cognition-cycle.sh: one command runs the full cycle — resolve cu, confirm core ping (parses .ok, never trusts cu's rc=0-on-refusal), resolve persona by name/UUID, snapshot every glass-box stream, run cu cognition/eval (single-pass or --gene A/B), delta the streams, write a timestamped report dir + print the VDD headline. Fails loud at the first missing precondition; --dry-run validates the chain spending no inference. - docs/architecture/HARNESS-RUNBOOK.md: the operational entry point (companion to PERFORMANCE-HARNESS-FRAMEWORK / OBSERVABILITY-AS-SUBSTRATE, not a restatement). Big picture, the one command, the capture-stream map (what question each answers), the where-time-goes lever ladder (GPU-first done → prefix/KV reuse → continuous batching → warming → genome learning), and battle-tested failure signatures. Validated end-to-end against the live core: the first real run produced a clean report AND earned its keep — it surfaced a degraded generation lane (every task emitted a 2-token stub at 0.34 tok/s; ps confirmed the only llama-server was in --embeddings mode). The report number flagged "something is wrong"; the glass box named what. That signature is now in the runbook's failure table. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * fix(cognition): gate the silence escape on directedness — Asha 0/13 → 4/13 The `[Silence Option]` block (reply the single word "PASS" to decline a turn) was offered UNCONDITIONALLY. A coder model handed an exam question as ambient chatter takes the trivial PASS exit — scoring 0/13 on the coder-eval gym because it declined every directed question instead of attempting it. Confirmed by curl A/B: strip the block → she answers; keep it → "PASS", regardless of wording. The faithful single-place fix is a structural addressing fact, not an output heuristic: `Workspace.directed_at_self`. When a turn is DIRECTED at the persona (examiner question / @mention / DM), withhold the silence affordance AND the soft "stay silent" tail — both gated in `compose_system(context, directed)`. Ambient turns keep silence first-class (it's legitimate to decline chatter). This is framing over a routing/ACL-shaped fact, not a function reading her output to puppet it. The eval pins `directed=true` as measurement control — the same kind of isolation as the greedy-temperature pin already in the deliberation faculty. It REVEALS coding capability (still graded by running tests), it does not inflate it. Wave 1 = zero live-behavior change: the live heartbeat passes `directed=false` (TODO #9 to compute real @mention/DM addressing via `contains_ascii_case_insensitive` and carry it on the replay trace), so the live ghost-a-direct-question gap is NAMED, not masked. KV-prefix safe: the silence block is the last static prefix segment, so toggling it invalidates only its own tail tokens; directedness is stable within a run (eval always directed, live ambient always undirected) so the cache holds. Validated: cargo check clean; new unit test `directed_turn_withholds_the_silence_escape` + act_observe/faculty/ workspace/eval modules green; harness 0/13 → 4/13 (self_verify 0.69, per-task table shows real attempts 1–8 acts, not blanket PASS). Remaining `no match` failures are model-capability shaped — the genome-loop mountain, not a substrate knob. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * docs(harness): correct failure-signature row 1 — PASS was the silence escape, not a dead lane The runbook blamed `--embeddings`-mode llama-server for the 0/13 stub. That was wrong: the directedness-gate fix (e49ae207e) lifted Asha to 4/13 on the SAME embeddings-mode lane. The ~2-token `"PASS"` was the persona taking the `[Silence Option]` escape on a directed exam turn, not a degraded generation lane. `--embeddings` is a red herring; the row now says so and points at the real confirm (jq the reply == "PASS", check the system prompt for the silence block) + the real fix (eval pins directed=true). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(harness): instrument prefill/decode split — make speed a measurable number Speed was one conflated wall-clock tok/s. The diagnosis showed 77% of eval time is PREFILL (re-encoding the ~2000-token prompt every settle-loop act), not decode — but nothing surfaced that, so the dominant inefficiency was invisible to the harness. This threads llama-server's per-call `timings` (which the adapter was DROPPING) all the way into the eval report. - ai/types.rs: GenerationTiming struct (cached/prefill/decode tokens + ms + tok/s); Option<GenerationTiming> on TextGenerationResponse → auto-flows into the glass box (~/.continuum/fixtures/prompt-captures) via the existing serde_json::to_value(response) capture, no writer change. - openai_adapter.rs: parse the streaming final-frame `timings` (cache_n, prompt_n, prompt_ms, predicted_n, predicted_ms, ...) → populate timing. Other adapters (cloud/canned) carry timing: None. - workspace.rs TurnMetrics: cached_tokens/prefill_tokens/prefill_ms/decode_ms fold across acts; decode_tokens_per_second() (lane clock, undiluted) and cache_hit_rate() derived; wall-clock tokens_per_second() kept as the diluted gross number. - llm_deliberation_faculty.rs metrics_from: single response→TurnMetrics map carries the timing. - eval.rs: per-task (EvalTaskResult) + aggregate (CognitionEvalResult) gain real decode tok/s, cache-hit-rate, and the prefill_ms-vs-decode_ms split; SpeedAggregates collapses the growing tuple to one named carrier; serialized into the progress ledger JSON. Validated: cargo check clean; eval module tests pass (prefill/decode fold + derived rates off the lane clock, not wall-clock); live llama-server :58057 streaming frame confirms the exact `timings` field shape the parser reads. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo * feat(harness): surface prefill/decode split + cache-hit in the report The Rust wave (03f5c65ba) plumbs llama-server's `timings` into the eval result; this surfaces it. The headline box gains a `speed` line — real decode tok/s vs diluted wall tok/s, cache-hit rate, and prefill's share of lane time — and the per-task table gains `decode tok/s` and `cache_hit` columns. Speed is now a measurable, iterable number, not one conflated wall-clock figure. First live numbers (Asha, single-pass): decode 29.7 tok/s (real) vs wall 10.7 tok/s; cache_hit 0.73; prefill 69% of lane time — confirming LIVE that re-prefilling dominates wall-clock. That 69% / 0.73 pair is the lever KV-cache locality attacks next. Also hardens cu_json: the refusal sniff (substrate refused / FAIL / error:) now runs ONLY when the output is not valid JSON. A real eval payload is always JSON; a refusal is plain stderr text. Without this, an answer whose text happened to cont…
joelteply
added a commit
that referenced
this pull request
Jul 8, 2026
…aceCaptureSink so a MEASURED run is readable (#1948) Opt-in `CognitionEvalParams.capture_dir: Option<String>` (None → Noop, zero change to existing paths). When set, cognition/eval wraps the fork's cognition via `cycle.with_capture(JsonlWorkspaceCaptureSink::open(dir, persona))` BEFORE the run, so every tick's bids + DECISION + timings append to <dir>/<persona>.jsonl. This is task #14's own primitive, wired into the measurement path — you can now read WHY a persona scored what she did (did she Act or Respond? which tool? did deliberation fail?), not just the final number. VALIDATED live + immediately earned its keep: pointed at a flask SWE run, it revealed the real cause of the persona's 0-byte diff — ALL 15 deliberation ticks failed with the local llama-server returning 500 "Compute error" (confirmed by a direct `say hi` probe → HTTP 500). Not exam-framing, not tools, not the model — a broken local inference lane. The 0-edit read itself off the trace in one line. run_ours.py now passes --capture_dir. Claude-Session: https://claude.ai/code/session_01LoTjvf5j3Ez13g6k8mRkFo Co-authored-by: Claude Opus 4.8 <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.
Bumps @types/node from 18.19.86 to 22.14.0.
Commits
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting
@dependabot rebase.Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR:
@dependabot rebasewill rebase this PR@dependabot recreatewill recreate this PR, overwriting any edits that have been made to it@dependabot mergewill merge this PR after your CI passes on it@dependabot squash and mergewill squash and merge this PR after your CI passes on it@dependabot cancel mergewill cancel a previously requested merge and block automerging@dependabot reopenwill reopen this PR if it is closed@dependabot closewill close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually@dependabot show <dependency name> ignore conditionswill show all of the ignore conditions of the specified dependency@dependabot ignore this major versionwill close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this minor versionwill close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)@dependabot ignore this dependencywill close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)