Skip to content

Nemotron qualification framework (bench/psyche-ab-matrix) — DRAFT until live plan runs - #1492

Draft
hartsock wants to merge 48 commits into
mainfrom
feat/psyche
Draft

Nemotron qualification framework (bench/psyche-ab-matrix) — DRAFT until live plan runs#1492
hartsock wants to merge 48 commits into
mainfrom
feat/psyche

Conversation

@hartsock

@hartsock hartsock commented Jul 30, 2026

Copy link
Copy Markdown
Member

DRAFT — qualification-ready harness work; do not merge yet.
This remains the long-lived feat/psyche integration branch. The live Nemotron lanes below have not been executed, so this PR makes no new performance claim.

What this PR does

Introduces the agent psyche: orthogonal controls for how much inference work is requested, how hard the harness pushes toward action, and whether a crew participates.

  • Adds the cognition dial (glancing, pondering, deliberating, contemplating) and threads it through personas, TUI, and headless turns. OpenAI Responses maps it to reasoning.effort; explicitly capable Chat Completions endpoints map it to a backend-neutral generation policy. Unknown endpoints receive no new request fields.
  • Surfaces tenacity and crew through persona configuration and adds /cognition, /psyche, and the obsessive posture.
  • Preserves and replays reasoning only within the current user turn, redacts it from older turns and visible answers, records Chat Completions finish reasons, and bounds reasoning-only continuation.
  • Makes declared local context windows authoritative while reserving the requested generation budget. The effective input ceiling is the minimum of the configured percentage and context_window - max_output_tokens.
  • Normalizes input_ceiling_pct to 1..=99, applies the same effective ceiling to preflight, compaction, context reporting, accepted-cap learning, and 400 recovery, and persists learned hard windows without permitting later raises.
  • Applies resolved runtime configuration through one canonical operation for both ordinary resolution and explicit --config solves. Nemotron family tenacity and CLI backend overrides now reach actual runtime behavior and contract output.
  • Records the cognition, tenacity, crew posture, effective model, digest, context window, and finish behavior actually used by a headless solve.
  • Makes bench/psyche-ab-matrix/run-matrix.sh capability-aware and fail-closed: it validates two captured preflight requests, retains immutable candidate/server/model/template/parser/workspace provenance, collects every cell, and exits nonzero for incomplete, mismatched, failed, or unverifiable qualification evidence.

The branch is rebased onto main after merged PR #1519 (ad7a69d).

Test plan

Local acceptance at 71bf419:

  • cargo build --workspace
  • cargo test --workspace
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo fmt --all -- --check
  • just cov-ci — 87.44% line coverage, 80% floor
  • python3 -B -m unittest discover -q -s bench/psyche-ab-matrix/tests -p "test_*.py" — 49 passed
  • bash -n bench/psyche-ab-matrix/run-matrix.sh
  • shellcheck bench/psyche-ab-matrix/run-matrix.sh

The Rust regression coverage includes all four cognition levels at 32K and 65K, zero and invalid percentages, percentage/output-reserve composition, numbered and numberless 400 recovery, persisted hard-window behavior, explicit-profile Nemotron family tenacity, explicit-profile CLI backend precedence, conservative unknown Chat cognition, and model-issued crew execution.

The harness tests exercise capability projection, first-request capture, strict tool schemas, same-turn reasoning replay, provenance cross-binding, Git workspace baselines, symlink and path-escape rejection, mode-downgrade rejection, timeouts, missing artifacts, model mismatch, and aggregate fail-closed behavior.

Nemotron qualification status

Plan stage Status
Harness reasoning/generation repairs Implemented and mocked
Context-budget correctness Implemented and mocked
vLLM qwen3_coder + nano_v3 reference lane Not run
C1–C10 live contract suite Not run
llama.cpp Q8_0 portability lane Not run
Six-task multi-round pilot Not run
Digest-pinned paired tb-30 Not run
New Nemotron performance result None

The live reference lane still requires a pinned model digest, native qwen3_coder tool parser, nano_v3 reasoning parser, declared context window, large output allowance, server/version identity, template/parser identity, and retained raw evidence.

Out of scope

  • Declaring PR Nemotron qualification framework (bench/psyche-ab-matrix) — DRAFT until live plan runs #1492 ready or merging it. Keep it draft until the live qualification plan is executed and reviewed.
  • Claiming a Nemotron or Terminal-Bench performance result from mocked or exploratory evidence.
  • Automatically launching vLLM or llama.cpp, downloading weights, or mutating inference-server configuration.
  • Inferring endpoint capabilities from a model display name. Unknown endpoints remain conservative until explicitly configured or successfully probed.
  • Responses API behavior changes, parallel provider process pooling, and the advanced slider TUI.

Co-authored-by: Claude Opus 4.8 (1M context) [email protected]
Co-authored-by: Codex [email protected]


Note

Medium Risk
Large new qualification tooling with strict validation and subprocess/network probes; risk is operational (misconfigured runs, sensitive bundles) rather than production runtime changes. Committed exploratory runs/ artifacts are low sensitivity but add repo noise.

Overview
Adds bench/psyche-ab-matrix, a fail-closed Psyche × OCAP qualification runner for Nemotron (vLLM reference + llama.cpp portability): run-matrix.sh drives the full 4×2 posture/OCAP grid, renders Nemotron capability TOML, probes server identity, runs loopback two-request cognition preflight, hashes harness/Newt sources, records per-cell Git/just baselines, and only passes after validate-run.py checks contracts, provenance, and artifacts. Supporting pieces include qualification_harness.py, loopback-preflight.py, three self-verifying tasks, extensive offline tests, README, and gitignore for sensitive run bundles; sample exploratory runs/ CSV/Markdown snapshots are included.

tmux-drive bumps to 2.1.0 with a Windows addendum and new scripts/wdrive.py (Tui over ConPTY/pywinpty) for in-process TUI automation, plus skill exec allowlist updates for python and winpty.

Reviewed by Cursor Bugbot for commit 71bf419. Configure here.

hartsock added a commit that referenced this pull request Jul 31, 2026
…ier is warning-clean

The R4 panel rework added `PsychePersonaAction {Keep, Clear, Switch}` and
`PersonaStore::save`, both constructed only inside the `#[cfg(feature =
"rich-tui")]` panel-outcome mapping. Under `--no-default-features` (the lean CI
firewall, #1409) the panel doesn't exist, so `Clear`/`Switch` and `save` are
never constructed → `-D dead-code` failed the `clippy (lean, no default
features)` job on PR #1492.

Why it slipped the local checks: the two lean *test* steps stayed green because
`cargo test` doesn't apply `-D warnings`; local clippy passed because
`--features newt-data/kernel` pulls newt-cli's default `rich-tui` in via feature
unification — the lean job strips it — and `--no-verify` skipped the local lean
gate. Reproduced with the exact CI command `cargo clippy -p newt-agent
--no-default-features --all-targets -- -D warnings`.

Rather than an `allow(dead_code)`, gate the whole panel path behind `rich-tui`
with a lean fallback, so no lint is suppressed and the lean tier is honestly
warning-clean. `active_backend_name` / `apply_persona_backend` have other lean
call sites, so there is no dead-code cascade.

What this PR does:
- `#[cfg(feature = "rich-tui")]` on `PsychePersonaAction`, `PsychePanelResult`,
  `run_psyche_panel`, and `PersonaStore::save`.
- `/psyche edit` handler: rich-tui body + a `#[cfg(not(feature = "rich-tui"))]`
  fallback pointing at the text `/psyche` + per-dial commands (same text the
  launcher printed before).
- Behaviour unchanged in both configs (rich: identical; lean: same fallback).

Test plan:
- `cargo clippy -p newt-agent --no-default-features --all-targets -- -D warnings`
  → clean (this was the failing job).
- `cargo clippy --workspace --all-targets --features newt-data/kernel -- -D warnings`
  (rich + live-spill + embedded) → clean; `-p newt-tui --features rich-tui
  --all-targets` → clean; `cargo fmt --all -- --check` → clean.
- `cargo test -p newt-agent --no-default-features --no-run` → links; rich
  `config_panel` unit tests pass.

Out of scope:
- The pre-existing local trybuild flake (#1496) and mcp_cli fixture leak (#1494).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
@hartsock hartsock self-assigned this Jul 31, 2026
@hartsock hartsock added the enhancement New feature or request label Jul 31, 2026
@hartsock

hartsock commented Aug 1, 2026

Copy link
Copy Markdown
Member Author

Implementation update from OpenAI Codex (GPT-5), Codex desktop harness on Windows.

Three focused commits are now on feat/psyche:

  • ed3c0b9 preserves split and inline assistant reasoning only across tool rounds in the current human turn, redacts old-turn reasoning, and protects the active reasoning/tool suffix during compaction and cap exit.
  • 707be4d adds explicit Chat Completions endpoint capabilities and projects psyche cognition into backend-neutral local generation controls. Unknown OpenAI-compatible endpoints remain byte-for-byte unchanged; no model-name heuristic is used.
  • 6a99153 classifies the exact reasoning-only finish_reason=length signature, permits one capability/replay/round-budget-gated continuation, records every Chat Completions finish reason, and emits bounded recovery outcomes to newt solve JSONL traces.

All new HTTP tests use wiremock; no live inference service or API key is used by the automated suite. Local verification at 6a99153: cargo fmt --all -- --check, cargo build --workspace, workspace all-target checks, strict workspace clippy, 1,935 core tests passed (2 ignored), and 384 CLI tests passed. CI is running for the pushed head.

hartsock added a commit that referenced this pull request Aug 1, 2026
…ier is warning-clean

The R4 panel rework added `PsychePersonaAction {Keep, Clear, Switch}` and
`PersonaStore::save`, both constructed only inside the `#[cfg(feature =
"rich-tui")]` panel-outcome mapping. Under `--no-default-features` (the lean CI
firewall, #1409) the panel doesn't exist, so `Clear`/`Switch` and `save` are
never constructed → `-D dead-code` failed the `clippy (lean, no default
features)` job on PR #1492.

Why it slipped the local checks: the two lean *test* steps stayed green because
`cargo test` doesn't apply `-D warnings`; local clippy passed because
`--features newt-data/kernel` pulls newt-cli's default `rich-tui` in via feature
unification — the lean job strips it — and `--no-verify` skipped the local lean
gate. Reproduced with the exact CI command `cargo clippy -p newt-agent
--no-default-features --all-targets -- -D warnings`.

Rather than an `allow(dead_code)`, gate the whole panel path behind `rich-tui`
with a lean fallback, so no lint is suppressed and the lean tier is honestly
warning-clean. `active_backend_name` / `apply_persona_backend` have other lean
call sites, so there is no dead-code cascade.

What this PR does:
- `#[cfg(feature = "rich-tui")]` on `PsychePersonaAction`, `PsychePanelResult`,
  `run_psyche_panel`, and `PersonaStore::save`.
- `/psyche edit` handler: rich-tui body + a `#[cfg(not(feature = "rich-tui"))]`
  fallback pointing at the text `/psyche` + per-dial commands (same text the
  launcher printed before).
- Behaviour unchanged in both configs (rich: identical; lean: same fallback).

Test plan:
- `cargo clippy -p newt-agent --no-default-features --all-targets -- -D warnings`
  → clean (this was the failing job).
- `cargo clippy --workspace --all-targets --features newt-data/kernel -- -D warnings`
  (rich + live-spill + embedded) → clean; `-p newt-tui --features rich-tui
  --all-targets` → clean; `cargo fmt --all -- --check` → clean.
- `cargo test -p newt-agent --no-default-features --no-run` → links; rich
  `config_panel` unit tests pass.

Out of scope:
- The pre-existing local trybuild flake (#1496) and mcp_cli fixture leak (#1494).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
@hartsock

hartsock commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Addressed the review findings in two commits after rebasing onto merged PR #1519:

  • ae00d15 composes input ceilings with generation reserves, normalizes the percentage, persists hard windows across recovery, canonicalizes runtime config application, and proves explicit-profile Nemotron tenacity/backend behavior through real subprocess tests.
  • 71bf419 declares and preflights the Nemotron capability profile, binds retained provenance and workspace baselines, and makes the matrix aggregate fail-closed.

Local acceptance is green: workspace build/test/clippy/fmt, 49 harness tests, and just cov-ci at 87.44% against the 80% floor.

This remains draft. I did not run the vLLM qwen3_coder + nano_v3 lane, C1–C10 live suite, llama.cpp portability lane, six-task pilot, or digest-pinned tb-30, so there is no new Nemotron performance result.

@hartsock

hartsock commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Final remote verification: every GitHub check on 71bf4198b83bafbefe754d43dc87f8b67aced8c6 is green, including both push- and PR-triggered Rust tests, fmt/clippy, Windows, MSRV 1.88, coverage, lean/no-default-features, security, mesh integration, and newt-web headless acceptance. The PR remains draft because the live Nemotron qualification lanes are still unrun.

@hartsock
hartsock marked this pull request as ready for review August 2, 2026 10:12
@hartsock hartsock changed the title feat(psyche): agent psyche dials — cognition/tenacity/crew + personas on gpt-5.6-sol [DRAFT] feat(psyche): agent psyche dials — cognition/tenacity/crew + personas on gpt-5.6-sol Aug 2, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 71bf419. Configure here.

):
required_labels.add(f"tasks/{task}/setup.sh")
for missing in sorted(required_labels - labels):
self.error(f"harness source manifest lacks required source {missing}")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validator reads live setup.sh

Medium Severity

Qualification re-validation decides whether tasks/{task}/setup.sh must appear in harness-sources.json by calling is_file() on the manifest’s tasks_dir at validation time, not from the retained snapshot. A later-added setup.sh on disk can fail an otherwise valid historical bundle; a removed path can skip checks that the snapshot still recorded.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71bf419. Configure here.

@@ -0,0 +1,39 @@
# Psyche × OCAP A/B matrix — ornith-1.0-35b-q8

Endpoint `http://192.168.0.103:8080` · each cell = pass-rate over the task set + avg tokens/wall per task.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Committed runs ignore gitignore

Low Severity

This change adds exploratory matrix CSV/Markdown under bench/psyche-ab-matrix/runs/ even though the harness .gitignore excludes runs/ and the README says run bundles must not be committed. The ornith sample also records a private LAN endpoint, which conflicts with the stated retention policy.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71bf419. Configure here.

| obsessive | off | write-greeting | pass | completed | 1 | 1 | 6133 | 3.2 |
| obsessive | on | edit-version | pass | completed | 2 | 1 | 7059 | 16.1 |
| obsessive | on | fix-typo | pass | completed | 2 | 1 | 6391 | 5.1 |
| obsessive | on | write-greeting | pass | completed | 1 | 1 | 6128 | 3.1 |

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exploratory run summaries committed

Medium Severity

This change adds exploratory matrix outputs under bench/psyche-ab-matrix/runs/ while also introducing a .gitignore that excludes runs/ and qualification-runs/. The README states those bundles should stay out of git. The ornith summary exposes a private LAN endpoint (192.168.0.103), and the CSV schema lacks the current events_file column, so these files read as run evidence but cannot pass today’s validator.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 71bf419. Configure here.

@hartsock

hartsock commented Aug 2, 2026

Copy link
Copy Markdown
Member Author

Before merge:

The implementation and mocked protocol coverage are substantially stronger now, but I do not think this is ready to merge until we execute and review the live Nemotron qualification plan.

Please treat the following as merge blockers:

  • Run the direct C1–C10 contract suite against vLLM using a pinned Nemotron model revision, the native "qwen3_coder" tool parser, the "nano_v3" reasoning parser, an explicit context window, and a sufficiently large output-token allowance.
  • Retain the vLLM evidence bundle, including the launch command, server and version identity, model revision or digest, template and parser configuration, and raw request and response bodies for the diagnostic rounds.
  • Run a six-task, multi-round pilot on vLLM with OCAP held constant. The tasks should include dependent tool calls, plan retention across tool results, failed-command recovery, multi-file editing, and final verification.
  • Run the same C1–C10 suite and six-task pilot against llama.cpp Q8_0 as the portability lane.
  • Classify differences between vLLM and llama.cpp as template, parser, transport, quantization, server, or harness behavior rather than combining them into one aggregate score.
  • Exercise "glancing", "pondering", "deliberating", and "contemplating" independently in live runs. Mock tests prove the configuration mapping, but not the model behavior.
  • Publish a failure taxonomy that separates model failures from malformed tool calls, lost reasoning state, output truncation, parser failures, context-budget failures, command failures, verification failures, and OCAP denials.
  • Run the full Psyche × OCAP matrix only after the fixed-OCAP contract and pilot runs pass. Otherwise we are changing too many variables at once to identify the source of a failure.
  • Run a digest-pinned, paired "tb-30" comparison against the previous Nemotron configuration and publish task-by-task deltas, not only the aggregate score.
  • Document the final acceptance decision and confirm that the new behavior does not regress known-good models, non-Nemotron backends, or the lean TUI path.

The loopback preflight is valuable and should remain. It proves that Newt constructs the expected request and performs bounded continuation after a reasoning-only truncation.

It does not prove that the inference server renders the correct Nemotron chat template, that the native reasoning and tool parsers reconstruct the response correctly, or that Nemotron can sustain a real multi-round terminal task.

I am comfortable reviewing the implementation in its current state. I am not comfortable treating green mocked tests as live Nemotron qualification evidence or merging this as a completed performance fix until the live artifact bundles above are attached and reviewed.

@hartsock
hartsock marked this pull request as draft August 3, 2026 11:43
@hartsock hartsock changed the title feat(psyche): agent psyche dials — cognition/tenacity/crew + personas on gpt-5.6-sol Nemotron qualification framework (bench/psyche-ab-matrix) — DRAFT until live plan runs Aug 3, 2026
@hartsock

hartsock commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Split per review (Story A)

This PR is now the draft Nemotron qualification framework. The production half — psyche dials, personas, runtime, reasoning replay, context budgeting, and the api=responses transport fix — moved to #1526 (targets main, mergeable on tests).

Blocker fixes landed here on feat/psyche (the qualification side): B2 hermetic evidence validation (13b1dbd), B3 reject-dirty-by-default (2106de7), B4 removed the committed run output that leaked the dgx1 LAN endpoint (9cb6d61), B5 relabelled the vLLM lane operator-attested (7a4bf6d, + provenance-binding tracked in #1525). B1 (ACP Responses transport) is production → on #1526.

Mechanics: feat/psyche still shows the production diff too. Once #1526 merges to main, rebase feat/psyche onto main and the production changes drop out — this PR then reduces to the qualification framework only.

Stays draft until the live plan runs: pinned vLLM C1–C10, the six-task multi-round pilot, the llama.cpp portability comparison, all four cognition postures, the failure taxonomy, the full Psyche×OCAP matrix, a paired digest-pinned tb-30, and regression checks for known-good non-Nemotron models.

hartsock and others added 7 commits August 3, 2026 17:05
…nacity/crew/backend)

A persona is already a saved collection of settings — role, tools, skills,
caveats, model, tier, altitude — loaded from a `.md` (front-matter + soul body).
This adds the "psyche" dimension: how hard the persona thinks and works.

- New `Cognition` dial (glancing → pondering → deliberating → contemplating),
  mapping onto the OpenAI `reasoning.effort` wire value (minimal/low/medium/high).
- New `RoleProfile` fields: `cognition`, `tenacity` (reuses the existing
  `newt_core::Tenacity` enum), `crew` (bool), and `backend` (names a
  `[[backends]]` entry a persona runs on, e.g. sol → gpt-5.6-sol).
- Threaded through `FrontMatter` parse, `is_role_bound`, and the `/persona`
  status display.

Declared-first, enforced-incrementally — exactly how `tools`/`model`/`tier`
already ship (RoleProfile.tools: "enforcement is a follow-up"). The emit sites
land next on this branch: `backend`→backend selection, `cognition`→the
`reasoning.effort` wire.

Ships two example personas: `bob.md` (a researcher on sol) and `obsessive.md`
(the max-dials "ultra" preset). Not yet seeded into DEFAULT_PERSONAS — that
waits until the dials are enforced, so no half-working default ships.

Tests: role_profile unit tests (fully mocked) cover parsing the psyche
front-matter, the cognition→reasoning-effort mapping, and that a lone dial makes
a persona role-bound. 31 role_profile tests green; clippy -D warnings clean.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…nses in solve/worker

The headless TurnDriver path (`newt solve`, worker) drove every OpenAI backend
over /v1/chat/completions, so a responses-only model like gpt-5.6-sol 400s:
"Function tools with reasoning_effort are not supported … use /v1/responses".

Root cause: `agentic/driver.rs::run_one_turn` had its OWN openai dispatch
(`if kind==Openai { openai_chat_complete(...) }`) that bypassed the
responses-vs-chat branch in `chat_complete_with_prompt_and_artifacts`. The
interactive chat path already routed correctly (chat.rs calls
`apply_openai_api_env`); only the headless path missed it.

- `run_one_turn` now mirrors the interactive dispatch: `responses_api_selected()`
  → `openai_responses_complete`, else `openai_chat_complete`.
- `solve.rs` surfaces the backend's `api = "responses"` into `NEWT_OPENAI_API`
  (via the now-`pub` `newt_tui::apply_openai_api_env`), exactly as the chat
  path does — so a `[[backends]] api = "responses"` entry works headless with
  no env fiddling.

Verified end-to-end on Windows: `newt solve --config <sol> …` against
gpt-5.6-sol now returns status=completed with a real reply (4214 tokens), where
it previously 400'd. driver + role_profile tests green; clippy -D warnings clean.

Follow-up (noted for review): the `apply_openai_api_env` writer arguably belongs
in newt_core next to its reader `responses_api_selected`, not in newt_tui.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… dispatch

Follow-up to 960e55e (addresses review: avoid duplicate execution paths). That
commit fixed the headless responses-routing bug by MIRRORING the
openai-responses-vs-chat dispatch inside `run_one_turn` — but that left the same
wire-format decision living in two places (`chat_complete_with_prompt_and_artifacts`
AND the driver), exactly the sprawl that makes "did we fix the bug, or is there a
second site?" unanswerable.

Root the decision in one owner instead. `run_one_turn` no longer forks on
`config.kind` — it just calls `chat_complete(ctx, &mut mcp)`, which already
routes openai-vs-ollama and, for openai, responses-vs-chat. `openai_chat_complete`
for the openai case with responses off is exactly what that entry resolves to, so
this is behaviour-preserving *and* removes the pre-existing openai/ollama fork the
driver carried. The wire-format dispatch now lives in exactly one function; the
headless driver and the interactive TUI go through it identically.

Net effect: three Cs — the routing knowledge (config: NEWT_OPENAI_API /
`api="responses"`) drives one cohesive dispatcher, and the two call paths are
loosely coupled to it rather than each re-deciding.

Verified: `newt solve` → gpt-5.6-sol still status=completed via /v1/responses;
driver tests 7/7; clippy -D warnings clean.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…ivalent

tmux-drive assumed a Unix box: no tmux exists on native Windows (Git Bash) and
WSL — the usual way to get one — needs a reboot. But the concept transfers: a
TTY-only program needs a real pseudo-terminal, and Windows has ConPTY (exposed
by pywinpty). This documents the Windows-native equivalent and ships a helper.

- New `scripts/wdrive.py` — the Windows sibling of `tdrive.sh`: a `Tui` class
  that spawns a console program in a pseudoconsole and gives send / wait /
  screen / quit. Requirements are no-admin/no-reboot (pywinpty + Git Bash's
  bundled winpty).
- New "Windows addendum" section in SKILL.md: the structural difference (a
  pseudoconsole is in-process, so you write a driver SCRIPT rather than
  one-subcommand-per-call — and there's no self-kill hazard), plus the two
  Windows gotchas: (1) cp1252 `print` crash on unicode → utf-8 stdout /
  PYTHONUTF8; (2) a pseudoconsole is a REAL tty, so isatty()-gated first-run
  work fires (for newt: the 468 MB on-host summarizer pull) → set the opt-out
  (NEWT_NO_MODEL_PULL=1).
- Frontmatter: exec caveat gains `python`/`winpty`; version 2.0.0 -> 2.1.0.

Verified on this Windows box: `wdrive.Tui` drives the real newt TUI end to end
and asserts the psyche persona display (bob shows role/backend/cognition/
tenacity with crew hidden; obsessive shows crew on).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
The cognition dial was declared on personas but inert. Route it to the
OpenAI Responses request through a single owner:

- ChatCtx gains `cognition: Option<Cognition>` (sibling of persona_tools).
- `responses_reasoning_field` is the one place that projects the level to the
  `{"effort": ...}` wire shape; the value mapping stays in
  Cognition::reasoning_effort. build_body emits it, omitting the field entirely
  when unset (bit-for-bit unchanged for non-opt-in callers).
- The TUI resolves it from the active persona's `cognition:` front-matter,
  co-located with persona_tools. Headless / eval callers pass None.

Chat-shape `reasoning_effort` emit is deferred on purpose: the chat path has
~7 request-body literals that must be consolidated to one owner first (called
out at the chat destructure) — scattering a field across all of them is the
exact sprawl the repo warns against.

Test plan: a wiremock test asserts reasoning.effort=high reaches the actual
/v1/responses body for cognition=contemplating, and that no reasoning field
is sent when unset; a pure unit test covers the level→effort mapping. clippy
--workspace --all-targets -D warnings and fmt --check clean.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Completes the cognition knob: the operator sets reasoning depth live,
mirroring /tenacity, without editing a persona file.

- newt-core::cognition — the session-override module (CognitionOverride
  Unset/Off/Set + resolve_cognition), the cognition analogue of tenacity's
  override. A separate tiny module on purpose: the two psyche dials act in
  different places — tenacity steers the harness LOOP (read at decision
  points), cognition rides the wire REQUEST (resolved into ChatCtx.cognition,
  whose single emit owner is responses_reasoning_field).
- Precedence, defined once in resolve_cognition: live /cognition override >
  the active persona's `cognition:` > None (no reasoning.effort).
- Cognition gains FromStr / all / describe / Display for the command surface.
- /cognition [level|off|auto|list] in commands/settings.rs, routed + helped in
  lib.rs next to /tenacity. `off` suppresses the field even under a persona;
  `auto` follows the persona (default).
- chat.rs resolves ctx.cognition via resolve_cognition(persona).

Test plan: newt-core module tests (unset→persona, set-wins-over-persona,
off-forces-none) + a settings command test (status/list/set/off/auto/error).
clippy --workspace --all-targets -D warnings + fmt --check clean.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Adds the read-only psyche panel: cognition, tenacity, and crew in one view,
each with how to change it. Reads the live session state — the cognition +
tenacity process-globals and the crew NEWT_TEAM startup gate (the same gate
newt-cli reads to build the crew runner) — so it reports what is actually in
effect, not what a persona merely declares (that stays /persona's job).

The three dials are kept factored on purpose: cognition rides the wire request,
tenacity steers the harness loop, crew sets how many minds — the panel names
"obsessive" as the max-everything posture (contemplating + relentless + crew on)
without yet wiring the macro (a follow-up, gated on the crew-is-startup design).

Routed + helped in lib.rs next to /cognition and /tenacity; a test asserts the
panel names all three dials and points at how to change each. Plain-scroller,
per docs/decisions/plain_scroller_tui.md — a text panel, not widgets/sliders
(those live in gilamonster-agent).

Test plan: settings command test (panel names cognition/tenacity/crew/obsessive
and the /cognition, /tenacity pointers). clippy --workspace --all-targets
-D warnings + fmt --check clean.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
hartsock and others added 26 commits August 3, 2026 17:05
…rsona state + ex-cmd validation

Reworks the #14 config panel (`/psyche edit`) to close the review-2 merge bar.
The panel is now handed the EFFECTIVE resolved posture and the active persona,
so it displays and saves the real values instead of the raw override.

What this PR does:
- review-2 #1: SAVE serializes the EFFECTIVE cognition/tenacity (`cognition_for_save`
  / `tenacity_for_save`) — a persona saved while the dial reads `auto` reproduces
  the resolved level (e.g. bob's `contemplating`), not an empty field.
- review-2 #2: cognition/tenacity dials gain a real `auto`/`inherit` ladder
  position that CLEARS the override on apply (cognition → `Unset`, tenacity →
  `clear_cli_tenacity`), so a value can return to persona/config resolution.
  Rendered as `auto → <effective>`.
- review-2 #4: the persona row shows the active persona with an `(active)` marker
  and maps `none` → Clear via a `PersonaAction {Keep, Clear, Switch}`; the session
  loop applies it (`persona clear` / `persona set … --keep-context` + backend
  re-route).
- review-2 #5: ex-commands validate with a VISIBLE magenta status line —
  unknown verb, missing name (`:w`), and existing-persona-without-bang
  (`:w!`/`:wq!` to overwrite) each report why, instead of failing silently.
- review-2 #3: the module doc labels this an INTERIM SURFACE — its ex-command
  reader does not yet reuse newt's shared vi/emacs editor core; unifying them is
  tracked as a follow-up.
- `run_psyche_panel` (lib.rs) + the `/psyche edit` branch (chat.rs) pass the
  effective snapshot in and act on the returned `PsychePersonaAction`.

Test plan:
- 5 pure `PanelState` unit tests under `GlobalSettingsGuard` (untouched dials not
  written, `auto` clears the override, save serializes effective, active marker +
  none→Clear, ex-commands validate visibly).
- v4 TUI-drive (`config_panel_drive.py`, pywinpty ConPTY): 9/9 — hint,
  auto/inherit labels, visible unknown/missing-name validation, `:w` writes a
  persona file + status, `(active)` marker, `none` clears the active persona.
- `cargo clippy -p newt-tui --features rich-tui --all-targets -- -D warnings` and
  the CI Windows leg `cargo clippy --workspace --all-targets --features
  newt-data/kernel -- -D warnings` both clean; `cargo fmt --all -- --check` clean.

Out of scope:
- Reusing the shared vi/emacs semantic-command layer in the panel (tracked
  follow-up; the panel is explicitly labeled interim until then).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…issue (#1495)

The vi/emacs editor-core reuse follow-up (review-2 #3) now has a home; link it
from the module doc so the INTERIM SURFACE label is actionable.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…ier is warning-clean

The R4 panel rework added `PsychePersonaAction {Keep, Clear, Switch}` and
`PersonaStore::save`, both constructed only inside the `#[cfg(feature =
"rich-tui")]` panel-outcome mapping. Under `--no-default-features` (the lean CI
firewall, #1409) the panel doesn't exist, so `Clear`/`Switch` and `save` are
never constructed → `-D dead-code` failed the `clippy (lean, no default
features)` job on PR #1492.

Why it slipped the local checks: the two lean *test* steps stayed green because
`cargo test` doesn't apply `-D warnings`; local clippy passed because
`--features newt-data/kernel` pulls newt-cli's default `rich-tui` in via feature
unification — the lean job strips it — and `--no-verify` skipped the local lean
gate. Reproduced with the exact CI command `cargo clippy -p newt-agent
--no-default-features --all-targets -- -D warnings`.

Rather than an `allow(dead_code)`, gate the whole panel path behind `rich-tui`
with a lean fallback, so no lint is suppressed and the lean tier is honestly
warning-clean. `active_backend_name` / `apply_persona_backend` have other lean
call sites, so there is no dead-code cascade.

What this PR does:
- `#[cfg(feature = "rich-tui")]` on `PsychePersonaAction`, `PsychePanelResult`,
  `run_psyche_panel`, and `PersonaStore::save`.
- `/psyche edit` handler: rich-tui body + a `#[cfg(not(feature = "rich-tui"))]`
  fallback pointing at the text `/psyche` + per-dial commands (same text the
  launcher printed before).
- Behaviour unchanged in both configs (rich: identical; lean: same fallback).

Test plan:
- `cargo clippy -p newt-agent --no-default-features --all-targets -- -D warnings`
  → clean (this was the failing job).
- `cargo clippy --workspace --all-targets --features newt-data/kernel -- -D warnings`
  (rich + live-spill + embedded) → clean; `-p newt-tui --features rich-tui
  --all-targets` → clean; `cargo fmt --all -- --check` → clean.
- `cargo test -p newt-agent --no-default-features --no-run` → links; rich
  `config_panel` unit tests pass.

Out of scope:
- The pre-existing local trybuild flake (#1496) and mcp_cli fixture leak (#1494).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…st projection, complete test isolation

Addresses the review-3 change request (transaction semantics + TUI state
integrity) on the #14 psyche config panel.

What this PR does:
- §1 TRANSACTION. Save is I/O-injected: `config_panel::run` takes a `persist`
  closure (the caller owns the `PersonaStore`), so the file write happens INSIDE
  the event loop and can keep the panel open on failure. `:wq` order is
  validate → persist → apply cognition/tenacity → (caller) apply persona + reroute
  → (caller) recompute + report. A failed persist returns `None` from the command,
  so the loop never breaks and `PanelState::apply` never runs — no dial, persona,
  or backend change. `PersonaStore::save` now writes atomically (temp file in the
  destination dir + rename) and refuses an existing persona without `!`; a failed
  or partial write never truncates the original. New `SaveResult`
  {Saved,Exists,InvalidName,Failed}; "saved persona" is shown only after the write
  lands.
- §2 EXPLICIT OUTCOME. `PanelOutcome` {Cancelled, Applied, Saved, SavedAndApplied}
  distinguishes cancellation from application — the panel returns NO summary
  string. On cancel the caller prints "psyche edit cancelled" (never a posture
  summary from the abandoned working copy); after an apply it builds the summary
  from freshly-resolved `effective_cognition()` / `effective_tenacity()` after the
  persona + backend commit.
- §3 HONEST PROJECTION. The panel receives each persona's declarations
  (`PersonaChoice`) plus the config/family tenacity base; selecting a persona
  recomputes the projected effective cognition/tenacity/backend/crew from THAT
  persona and labels provenance (override vs `persona: <name>` vs base/current).
  The active persona shows "(active)", a different selection "(pending)". Save
  serializes exactly the projected posture.
- §4 TEST ISOLATION. New `tenacity::{snapshot,restore}_runtime_state` +
  `TenacityRuntimeSnapshot` cover ALL FOUR tenacity globals — CLI, persona,
  `TENACITY_CONFIG`, `ACTIVE_FAMILY` (the last two were the audited gap: mutated
  by `Config::resolve` and the solve model-selection path, never restored). A
  symmetric `cognition::{snapshot,restore}_runtime_state` covers the two cognition
  globals. `GlobalSettingsGuard` now composes these two snapshots instead of
  reaching into each global piecemeal; its doc records the full audit. New
  `tenacity::{tenacity_config,active_model_family,base_tenacity}` accessors.
- §5 #1495 (shared vi/emacs input) stays visible: milestoned v0.8.0, linked from
  the panel doc; no new panel-specific key parsing was added, and the injected
  `persist` seam + structured outcome port cleanly onto the shared editor.

Test plan:
- newt-core lib: 1891 passed (incl. snapshot round-trip of all 4 tenacity globals,
  base_tenacity, and Drop-restores-on-panic).
- newt-tui lib (rich-tui): 681 passed (transaction: :wq success/fail-rollback,
  :w-then-cancel = Saved-not-applied, exists-without-bang; projection follows the
  selected persona / base; atomic save + overwrite-failure-preserves-original;
  ex-command validation).
- TUI-drive (`config_panel_drive.py`, real ConPTY): 10/10 — provider row +
  provenance render, visible validation, `:w` persists a file, CANCEL prints
  "psyche edit cancelled", APPLY prints a fresh posture summary, active/pending +
  projected values.
- `cargo fmt --all -- --check` clean; `cargo clippy --workspace --all-targets
  --features newt-data/kernel -- -D warnings` and `cargo clippy -p newt-agent
  --no-default-features --all-targets -- -D warnings` both clean.

Out of scope:
- The shared vi/emacs input consolidation (#1495, milestoned v0.8.0).
- Pre-existing Windows-local flakes: mcp_cli fixture leak (#1494) and the
  trybuild snapshot drift (#1496) — both CI-green on Linux, untouched here.
- `cargo clippy --all-features` is not buildable on Windows (macOS/CUDA deps).

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
Three review findings (all §2/§3 truthfulness, none blocking) from the
transaction review:

- Active persona absent from the projected list (e.g. its file failed to load)
  no longer maps Enter to a silent Clear: PanelState::new appends the active
  persona if missing, so it renders "(active)" and Enter = Keep.
- The saved-persona confirmation now echoes the store's NORMALIZED on-disk name
  (from the returned path stem), not the raw typed name — `:wq MyPersona` reports
  `saved persona 'mypersona'`, matching the file.
- The backend projection fallback for a persona that declares no backend is now
  the operator BASELINE (`base_provider` — what apply_persona_backend reverts to),
  not the outgoing persona's current backend; provenance labels it "base".

Test plan:
- New unit tests: active-persona-absent → Keep; no-backend persona projects the
  baseline; save returns the normalized on-disk name. config_panel 13 + persona
  40 pass; TUI-drive 10/10; clippy (newt-data/kernel + lean) + fmt clean.

Out of scope: unchanged from the parent commit.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
A reproducible bench that measures how the psyche dials + the OCAP lane affect a
headless `newt solve`, on ornith-1.0-35b-q8 (dgx1). Built to run unattended on
gnuc (or any host that can reach dgx1) — no ~/.newt or host-specific paths.

What this adds (bench/psyche-ab-matrix/):
- run-matrix.sh — sweeps {baseline, tenacity, crew, obsessive} × {OCAP off,on}
  over a task set, driving `newt solve` per cell; collects verify pass/fail +
  status/tool_calls/write_calls/tokens/wall; renders matrix.md + results.csv.
  Postures/OCAP are set purely via global flags + env (--tenacity/--obsessive,
  NEWT_TEAM=1, NEWT_BENCH_OCAP=on, --non-interactive true). Endpoint/model/tasks
  are env-overridable; a /v1/models 200 pre-check fails fast on a dead backend;
  python3→python fallback for cross-box portability.
- tasks/ — 3 small self-verifying tasks (write-greeting, edit-version, fix-typo),
  each instruction.txt + setup.sh + verify.sh.
- README.md — the reproduction recipe (endpoints: Tailscale 100.113.207.102:8080
  or LAN dgx1.home.lan:8080; how gnuc runs it).

Pre-flight validated on NUC01 against real ornith on dgx1: hello + find/edit both
work end-to-end, and baseline × off × 3 tasks = 3/3 verify pass (tool loop drives
real edits; ~6–8k tokens/turn — ornith is a heavy reasoner, budgets sized for it).

Note: ornith is a chat_completions backend, so the cognition dial (reasoning.effort,
Responses-only) is inert here — the axes that move ornith are tenacity + crew; the
matrix records all four postures so that null result is measured, not assumed.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…r delegate (#1320 PR-3)

Extracts the config-based backend precedence into one place — `Config::
select_configured_backend` (NEWT_PROVIDER > default_backend > sole >
prefer-OpenAI, else first usable) — and routes `solve::pick_backend` and the ACP
worker's `select_openai_backend` through it, so chat, solve, and the worker agree
on which configured backend the operator named. Retires solve's + the worker's
divergent pickers (the worker keeps its OpenAI-kind requirement for the vLLM
path, layered over the shared pick). The env-synthesized fallbacks (codex, legacy
dgx, localhost) stay in chat's `resolve_backend_choice`, around this.

Test plan: pick_backend tests 4/4 (default_backend precedence preserved); worker
compiles; fmt clean. Behavior-preserving for solve (adds the sole/prefer-OpenAI
rungs that match chat); the worker now honors NEWT_PROVIDER/default_backend when
they name an OpenAI backend.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…g (sol)

The matrix runner now drives any backend via MODEL_ENDPOINT / MODEL_ID /
MODEL_API / MODEL_KEY_FILE / BACKEND_NAME (ORNITH_* kept as aliases), so the same
sweep runs the ornith leg (chat_completions, dgx1) AND a sol leg
(api=responses, gpt-5.6-sol, keyed) — the only backend where the cognition dial
wires through. Auth-gated endpoints skip the unauthenticated reachability probe.
README documents both legs + the env table.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
#1139)

PR-4 of the #1320 psyche-config chain, landing on feat/psyche.

## What this PR does
- Add newt-core `RuntimeSettingsSnapshot` — the single resolved read model of
  the operator posture (cognition, tenacity, crew, active persona, and a
  layered `BackendState { configured, operator, persona, effective, model }`),
  resolved ONCE from the process globals + `Config` via
  `RuntimeSettingsSnapshot::resolve`. It REUSES the existing resolvers
  (`effective_cognition`, `effective_tenacity`, `Config::select_configured_backend`)
  rather than re-deriving them. Its `summary()` is now the single source for
  the chat `/psyche edit` apply line, which previously re-derived each dial inline.
- Fix the ACTIVE_FAMILY-in-chat gap: interactive chat never called
  `set_active_model_family`, so per-family `[tenacity.families]` defaults
  silently never applied there (only `solve` attributed a family). Add
  `tenacity::attribute_active_family(config, model)` and call it (a) at chat
  startup for the base model and (b) inside `refresh_backend` — the single seam
  every mid-session model change (`/backends`, `/model`, persona routing via
  `apply_persona_backend`) flows through — so the family tracks a live
  backend/persona switch instead of going stale on the startup model.

## Test plan
- `cargo test --workspace --features newt-data/kernel` — green.
- New unit tests (fully mocked, GlobalSettingsGuard-isolated):
  - runtime.rs: `resolve_reads_effective_dials_and_the_selected_backend`,
    `operator_pin_and_crew_gate_are_reflected`.
  - tenacity.rs: `attribute_active_family_installs_card_then_substring_then_clears`
    (card-family / name-substring / clear-on-no-match paths).
- `cargo clippy --workspace --all-targets --features newt-data/kernel -- -D warnings` — clean.
- `cargo fmt --all -- --check` — clean.

## Out of scope
- Threading one snapshot through dispatch / moving `BackendChoice` into core
  (the end-to-end #1139 ownership step) — tracked separately; runtime.rs is the
  read-model seam it grows from.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…; +x on runner/task scripts

Both legs of the psyche×OCAP A/B matrix (stamp 20260731-194124, newt 0.7.6
@4782bf7): 24/24 cells pass per leg, OCAP on≈off in every posture (parity
clean). NOTE for review: sol per-posture token usage is byte-flat (±5 tok
across 24 cells) — consistent with the P1 'dials display-only / cognition
dropped on the wire' findings; needs a wire-level request dump to adjudicate.
Exec bits restored on run-matrix.sh + task scripts (lost via Windows checkout;
verify.sh silently skipped without them).

Co-authored-by: Fable 5 <[email protected]>
The runs/ dir is ignored for workspaces/traces; these four small files are the
durable evidence (matrix.md grid + results.csv per leg) the board card cites.

Co-authored-by: Fable 5 <[email protected]>
Preserve split and inline assistant reasoning across tool rounds only when an endpoint explicitly opts into the current-user-turn replay contract. Keep unknown endpoints conservative and propagate the capability through TUI and headless solve paths.

Co-authored-by: Codex <[email protected]>
Compose input ceilings with generation reserves, persist learned hard windows, and apply resolved runtime configuration consistently across interactive and explicit-profile solves. Record the cognition, tenacity, and crew posture actually used by the turn.

Co-authored-by: Codex <[email protected]>
Activate the declared Chat Completions capability profile, validate captured request bodies, retain immutable runtime and workspace provenance, and reject incomplete or mismatched qualification cells after collecting the full matrix.

Co-authored-by: Codex <[email protected]>
…nvalidatable)

`bench/psyche-ab-matrix/runs/` is `.gitignore`d yet these four files were tracked.
`runs/20260731-194124-ornith/matrix.md` exposes `http://192.168.0.103:8080` — the
dgx1 private-LAN endpoint, which RATCHET.md forbids from any commit. The CSVs also
predate the current qualification schema, so they read as evidence while being
impossible to validate with the current validator. Nothing references them.

Removed rather than kept. Sanitized synthetic fixtures (against the current
schema, marked non-evidence) will be added under
`bench/psyche-ab-matrix/tests/fixtures/` if/when the validator tests need sample
data. NOTE: the IP remains in branch history; a history rewrite is inadvisable
here because feat/psyche is already the base of PR #1520 (step-28.1) — rewriting
it would break that stacked branch.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
…endently verified

The review is right that the vLLM lane's independent probes prove declaration
*consistency* (model name in /v1/models + server /version), not process
*identity* — the weight digest, context window, chat template, tool parser, and
reasoning parser all come from an operator-supplied launch manifest, and the
identity gate only proves that manifest is self-consistent. A stale/pre-existing
server matching the display name + version passes without any attacker.

The caveats were already in the README; this adds a prominent headline banner so
the top-level framing matches them: bundles are the operator's declaration checked
for internal consistency, NOT independently-verified qualification. Full
process-identity binding (harness-owned lifecycle or nonce-bound launch
attestation) is future work. (The llama.cpp lane is stronger — it independently
reads build_info/n_ctx/chat-template-hash from /props.)

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
The ACP worker constructed `LocalVllmBackend::from_config` for every OpenAI
backend and that adapter always posts to `/v1/chat/completions`, discarding
`BackendConfig.api`. So `newt worker` drove a Responses-only model (e.g.
gpt-5.6-sol, `api = "responses"`) over Chat Completions — recreating the exact
400-on-function-tools failure this line claims to fix. `newt solve` was already
correct because it reaches the Responses wire through the agentic loop; the
worker consumes the `InferenceBackend` seam directly and had no api-aware path.

Add `ResponsesBackend` (POST `/v1/responses`, `instructions` + `input`, parses
`output[].content[].output_text` with an `output_text` fallback, `input/output`
token usage) and a single `openai_inference_backend(cfg)` factory that selects
the transport from `cfg.api` with an EXHAUSTIVE match (a new `OpenAiApi` variant
must be handled, not silently defaulted). The ACP worker's OpenAI branch now
routes through the factory, so both the flat and coder ACP paths obey the wire
API. The seam carries the simple completion shape (messages → text); multi-turn
function-calling stays in the agentic loop.

Wire tests (wiremock): a responses config posts to `/v1/responses` and never to
`/v1/chat/completions` (`.expect(0)`), output-text extraction + usage parse, and
the factory selects Responses vs Chat Completions (and the unset default) by api.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
A dirty checkout made the bundle irreproducible: `commit` + the candidate binary
hash pinned something git could not reconstruct, and qualification mode accepted
it anyway (the validator only checked `harness.dirty` was a bool). Qualification
now fails closed on a dirty tree BEFORE any inference. An explicit
`ALLOW_DIRTY_QUALIFICATION=1` override still runs, but retains
`source-dirty.patch` (`git diff --binary HEAD`) and `source-dirty-status.txt`
(untracked listing) in the bundle so the run stays reproducible/inspectable.

A `FAKE_SOURCE_DIRTY` test hook (consistent with the runner's existing
`FAKE_EFFECTIVE_MODEL` / `FAKE_TIMEOUT_LOG`) drives the gate deterministically
without mutating the real repo. The existing fixture tests opt into the override
(they exercise the run + dirty-recording paths, and the dev/CI checkout may be
dirty). New tests: dirty + no override → non-zero exit with "clean source tree"
and no results produced; dirty + override → runs and retains the diff.

Follow-up (not this commit): retaining Cargo.lock, toolchain identity, and the
exact candidate binary on override — tracked separately.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
… (B2)

Historical bundle validation was time-dependent: the validator decided whether
`tasks/<task>/setup.sh` was a required source by `stat`-ing the LIVE external
task tree (`(Path(tasks_dir)/task/"setup.sh").is_file()`). So adding a setup.sh
later made an old bundle invalid, removing one made a required-source check
vanish, and moving/deleting the checkout changed the verdict — contradicting the
"validates against a pinned snapshot" claim.

The bundle already retains the task sources (harness-sources snapshot). The only
non-hermetic bit was which tasks REQUIRED a setup.sh. Pin that as a fact at run
time — `manifest.matrix.tasks_with_setup` (computed once from the task tree while
the runner is producing the bundle) — and have the validator derive the required
set from that pinned fact + the retained source manifest, never the live tree.
The field is schema-checked (a list, subset of matrix.tasks).

Regression test (discriminating): a produced bundle validates identically after
(a) a setup.sh is ADDED to the live tree post-run — the exact mutation the old
`is_file()` check reacted to — and (b) the original task tree is deleted
entirely. Validator fixtures pin the fact; the setup-source test now asserts the
hermetic contract (pinned-present ⇒ retained-source-required).

Note: proof paths remain host-absolute but self-consistent (derived from the
recorded tasks_dir via strict=False resolve, no fs access). Switching them to
bundle-relative logical paths is a larger follow-up, tracked separately.

Co-authored-by: Claude Opus 4.8 (1M context) <[email protected]>
hartsock added a commit that referenced this pull request Aug 5, 2026
…e-production

feat(psyche): agent psyche dials + personas + api=responses transport (production split of #1492)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant