Skip to content

feat(config): run omp as the no-mistakes pipeline agent and block pattern kills - #27

Merged
undeemed merged 8 commits into
mainfrom
fm/cf-sync-host-setup-y1
Oct 1, 2026
Merged

undeemed merged 8 commits into
mainfrom
fm/cf-sync-host-setup-y1

Conversation

@undeemed

@undeemed undeemed commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Intent

"once all is done, sync setup to code factory and my custom stuff to vps-setup"

"fuck the nvim, gonna just use lazy vim on the new machine, is everything else synced to code-facoty and vps-setup?"

Context for reading those asks: Code-Factory reproduces this fleet host from a fresh box, and the owner's rule from 2026-09-30 is that generic, public host provisioning lives in Code-Factory while personal material goes to the separate private vps-setup repo. Three Code-Factory PRs already merged today: #20 (omp status icons), #21 (live sidebar stats) and #22 (every tool installs at its latest release, codex/pi/pnpm removed, lanes on bun). The Neovim setup was dropped: Code-Factory ships no nvim config. Several generic changes made directly on the host since then are not in Code-Factory yet: omp running as the no-mistakes pipeline agent through the no-mistakes pi adapter (the omp-as-pi wrapper with its sha-pinned gate overlay, tests and adapter-hash check, with acp:omp as the fallback and the only agent when the adapter of the installed no-mistakes release does not match the pins), the omp settings overlay that only the no-mistakes daemon's omp loads (an Opus advisor), and the omp extension that blocks process kills selected by name or pattern in every omp session. A fresh box should get all of them on every apply.

What Changed

  • Adds config/omp-as-pi/, a wrapper that lets no-mistakes drive omp through its pi adapter. It includes a sha256-pinned gate overlay, check-adapter.sh (pins the adapter sources of the installed no-mistakes release) and an offline test.sh, which CI now runs. ansible/tasks/agents.yml installs the wrapper and runs the check on every apply. The pipeline agent becomes [pi, acp:omp] when the pins match, [acp:omp] alone when they differ, and is left unchanged when the check is inconclusive. verify.yml also runs the wrapper preflight.
  • Adds a config/no-mistakes-omp.yml overlay (Opus advisor, Sonnet default) that only daemon-spawned omp loads, through a no-mistakes-daemon-.service.d systemd drop-in that sets PI_CONFIG_FILES. It also adds the config/omp-no-pattern-kill.ts omp extension, installed every apply, which blocks pkill, killall and kill-by-pgrep in every omp session. Changing the daemon's omp overlay, drop-in or agent setting prints a manual daemon-restart reminder.
  • scripts/install_tools.py now resolves no-mistakes from the newest non-draft release, prereleases included. tests/test_check_adapter.py, test_omp_no_pattern_kill.py, test_configuration.py, test_install_tools.py and container-smoke.sh are added or updated, and the omp, configuration, dependencies, architecture, capacity and host-move docs are updated.

Risk Assessment

⚠️ Medium: All three requested pieces are wired in, the pins are correct for current releases, and the ansible switch logic is idempotent. The security-relevant pin omits ompgate.go, the apply can hang on an unbounded fetch, the kill guard has loose matching, and the overlay drops the default model pin on the fallback path.

Testing

I ran the targeted pytest files and the omp-as-pi offline suite (all passed). Both use stub curl and stub omp, so they are not counted as a live scenario. I then drove the change live: check-adapter.sh against the real no-mistakes tags on GitHub, the real Ansible agents step against a disposable HOME, the installed omp-as-pi wrapper with the real omp and a local mock model, and real omp sessions loading the pattern-kill extension. The overlay scenario was redone with PI_CONFIG_FILES unset, because my shell had inherited the host daemon's overlay. Step 4 of the kill guard was confirmed to return 'Blocked:' results. All my temp state is torn down and the worktree is clean. The only finding is that the kill guard over-blocks commands that merely mention pkill.

  • Live validation: ✅ go - 9 of 12 scenarios driven live against the product
Scenario Result Live Evidence
Adapter check passes when the installed no-mistakes release still matches the pins (check-adapter.sh against real GitHub) ✅ pass live check-adapter-live.log: v1.85.3, v1.85.2, v1.85.0, v1.84.0 and v1.83.2 each print 'pi adapter sources at <tag> match the pins' and exit 0
Adapter check separates a proven mismatch from an inconclusive fetch ✅ pass live check-adapter-live.log: altered pin against real v1.85.3 bytes gives 'changed in' and exit 1; v1.85.4 and a nonexistent tag give HTTP 404 and exit 1; an unreachable network gives HTTP 000 and exit 2;…
Fresh-box apply installs omp-as-pi, the daemon overlay, the systemd drop-in and the kill-guard extension, and moves the pipeline agent to [pi, acp:omp] in the same apply ✅ pass live ansible-agents-apply1.log and ansible-agents-state-and-apply2.log: config.yaml ends with agent [pi, acp:omp], agent_path_override.pi pointing at the installed wrapper, and sonnet-5-5 at effort high. A…
Adapter mismatch drops the agent to acp:omp alone; an inconclusive check or an operator-chosen agent is left alone ✅ pass live ansible-agents-scenarios-ABC.log: a pi host with release 1.85.4 becomes ['acp:omp'] and reports why. An unreachable GitHub leaves config.yaml byte-identical with a WARNING, and a rerun gives changed=0…
omp-as-pi with the real omp: preflight, cold step then resume of the same session, gate neutralization, fail-closed refusals ✅ pass live omp-as-pi-real-omp.log and omp-as-pi-wrapper-live.log: preflight ok on omp 18.4.8. The cold step emits a session id and the resume keeps that id, with the first prompt still in the resumed request's h…
Daemon-only omp settings overlay changes the default role to sonnet-5-5:high and turns on the Opus advisor, relative to the seeded omp config ✅ pass live omp-overlay-effective-config.log, run with PI_CONFIG_FILES unset and HOME seeded from config/omp.yml. The seed alone gives default opus-5-5:xhigh, advisor sonnet-5 and advisor.enabled=false. Adding th…
Kill guard extension blocks pkill, killall and kill-by-pgrep in a real omp session but allows kills by PID ✅ pass live omp-kill-guard-live.log: the pkill -f sentinel survives and the tool result is the 'Blocked:' reason. With --no-extensions the same pkill kills the sentinel. kill <pid> runs and kills it. In step 4, b…
Installer resolves no-mistakes from the prerelease channel, so the adapter check runs against the version actually installed ✅ pass live install-tools-resolve-live.log: with GitHub's latest-stable endpoint at 1.84.0 and 1.85.3 published as a prerelease, --resolve returns 1.85.3 with its SHA-256
Code-Factory ships no nvim config and the default configuration still validates ✅ pass live validate-and-no-nvim.log: factory validate prints 'Valid host configuration', exit 0; zero tracked nvim/neovim/lazyvim files or mentions; the disposable-HOME apply created no nvim files
Targeted existing tests: check-adapter, kill-guard, install_tools, configuration and the omp-as-pi offline suite ⏸️ untested no The prior payload ran these only as pytest (98 passed) and the omp-as-pi offline suite (PASS), which use stub curl and stub omp. It recorded live=false, so it did not establish a live result for this…
The real no-mistakes daemon loads the systemd drop-in, so daemon-spawned omp reads the overlay ⏸️ untested no Starting or restarting the real no-mistakes daemon would kill pipeline runs in flight on this host, and the workspace boundary forbids changing the host's user systemd units. I did not build a disposa…
Personal material is synced to the private vps-setup repo ⏸️ untested no vps-setup is a separate private repository outside this worktree. I have no access to it and no credentials for it, and this change contains none of its content, so there is nothing in this run to dri…
Evidence: check-adapter.sh live against GitHub (match, altered pin, 404, unreachable, usage)
\### check-adapter.sh v1.85.3
pi adapter sources at v1.85.3 match the pins
exit=0
\### check-adapter.sh v1.85.4
internal/agent/pi.go is gone from v1.85.4 (HTTP 404); re-prove omp-as-pi against v1.85.4 first
exit=1
\### check-adapter.sh v0.0.0-nonexistent
internal/agent/pi.go is gone from v0.0.0-nonexistent (HTTP 404); re-prove omp-as-pi against v0.0.0-nonexistent first
exit=1
\### no arg
usage: check-adapter.sh <tag>
exit=64
\### v1.85.2
pi adapter sources at v1.85.2 match the pins
exit=0
\### v1.85.0
pi adapter sources at v1.85.0 match the pins
exit=0
\### v1.84.0
pi adapter sources at v1.84.0 match the pins
exit=0
\### v1.83.2
pi adapter sources at v1.83.2 match the pins
exit=0
\### copy with the pi.go pin altered (real v1.85.3 bytes, real GitHub)
internal/agent/pi.go changed in v1.85.3 (sha cd5340ef362585592fc1fdaf856a62cdfa3303dde5d324d93b23998c4c0f8d75); re-prove omp-as-pi against v1.85.3 first
exit=1
Evidence: Fresh-box agents.yml apply (ansible-playbook, disposable HOME)
\### apply #1 (fresh disposable HOME, no-mistakes 1.85.3, real check-adapter over real network)
PLAY [all] *********************************************************************
TASK [Ensure the harness preference directories] *******************************
changed: [localhost] => (item=/tmp/cf-home1/.omp/agent)
ok: [localhost] => (item=/tmp/cf-home1/.omp/agent)
changed: [localhost] => (item=/tmp/cf-home1/.no-mistakes)
changed: [localhost] => (item=/tmp/cf-home1/.acpx)
TASK [Check for existing harness preference files] *****************************
ok: [localhost] => (item=/tmp/cf-home1/.omp/agent/config.yml)
ok: [localhost] => (item=/tmp/cf-home1/.omp/agent/lsp.json)
ok: [localhost] => (item=/tmp/cf-home1/.no-mistakes/config.yaml)
ok: [localhost] => (item=/tmp/cf-home1/.acpx/config.json)
TASK [Write harness preferences that are absent] *******************************
changed: [localhost] => (item=/tmp/cf-home1/.omp/agent/config.yml)
changed: [localhost] => (item=/tmp/cf-home1/.omp/agent/lsp.json)
changed: [localhost] => (item=/tmp/cf-home1/.no-mistakes/config.yaml)
changed: [localhost] => (item=/tmp/cf-home1/.acpx/config.json)
TASK [Report harness preferences left untouched] *******************************
ok: [localhost] => {
    "msg": "none\nalready exist and were not rewritten (first-write-only inputs)."
}
TASK [Install ponytail-review] *************************************************
changed: [localhost]
TASK [Ensure the omp extensions directory] *************************************
changed: [localhost]
TASK [Install the Herdr sidebar omp extension] *********************************
changed: [localhost]
TASK [Install the status icons omp extension] **********************************
changed: [localhost]
TASK [Install the pattern-kill guard omp extension] ****************************
changed: [localhost]
TASK [Install omp-as-pi, the wrapper that runs omp as the no-mistakes pi agent] ***
changed: [localhost]
TASK [Install the omp overlay that only daemon-spawned omp loads] **************
changed: [localhost]
TASK [Ensure the no-mistakes daemon drop-in directory] *************************
changed: [localhost]
TASK [Point daemon-spawned omp at the overlay] *********************************
changed: [localhost]
TASK [Check the pi adapter of the no-mistakes release this run installs] *******
ok: [localhost]
TASK [Read the no-mistakes config for the pipeline agent] **********************
ok: [localhost]
TASK [Set the no-mistakes pipeline agent from the pi adapter check] ************
changed: [localhost]
TASK [Report why the no-mistakes pipeline agent stays acp:omp] *****************
skipping: [localhost]
TASK [Warn that the no-mistakes pi adapter could not be verified] **************
skipping: [localhost]
TASK [Read the omp config for the status row keys] *****************************
ok: [localhost]
TASK [Ensure the status row keys in the omp config] ****************************
skipping: [localhost]
TASK [Read the acpx config for the default agent] ******************************
ok: [localhost]
TASK [Point the acpx default agent at omp when it is still codex] **************
skipping: [localhost]
RUNNING HANDLER [reload user systemd] ******************************************
ok: [localhost] => {
    "msg": "HANDLER reload user systemd fired"
}
RUNNING HANDLER [no-mistakes daemon restart required] **************************
ok: [localhost] => {
    "msg": "HANDLER no-mistakes daemon restart required fired"
}
PLAY RECAP *********************************************************************
localhost                  : ok=20   changed=12   unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
Evidence: Resulting files, config.yaml, drop-in, and second apply with changed=0
\### resulting files
-rw------- ./.acpx/config.json
-rw-r--r-- ./.config/systemd/user/no-mistakes-daemon-.service.d/code-factory.conf
-rwxr-xr-x ./.local/bin/ponytail-review
-rw------- ./.no-mistakes/config.yaml
-rwxrwxr-x ./.no-mistakes/omp-as-pi/check-adapter.sh
-rw-rw-r-- ./.no-mistakes/omp-as-pi/gate-overlay.yml
-rwxrwxr-x ./.no-mistakes/omp-as-pi/omp-as-pi
-rwxrwxr-x ./.no-mistakes/omp-as-pi/test.sh
-rw------- ./.no-mistakes/omp-config.yml
-rw------- ./.omp/agent/config.yml
-rw-r--r-- ./.omp/agent/extensions/aa-mode-icons.ts
-rw-r--r-- ./.omp/agent/extensions/code-factory-herdr-sidebar.ts
-rw-r--r-- ./.omp/agent/extensions/fm-no-pattern-kill.ts
-rw------- ./.omp/agent/lsp.json
\### no-mistakes/config.yaml
agent:
- pi
- acp:omp
agent_path_override:
  pi: /tmp/cf-home1/.no-mistakes/omp-as-pi/omp-as-pi
agent_config:
  pi:
    model: anthropic/claude-sonnet-5-5
    effort: high
\### systemd drop-in
# Managed by Code Factory (docs/omp.md#no-mistakes-pipeline-agent).
[Service]
Environment="PI_CONFIG_FILES=/tmp/cf-home1/.no-mistakes/omp-config.yml"
\### nvim anywhere?
0
\### apply #2 recap (idempotence)
PLAY RECAP *********************************************************************
localhost                  : ok=16   changed=0    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
Evidence: Pin mismatch, inconclusive fetch, and operator-chosen agent scenarios
##### A: host on [pi, acp:omp]; release 1.85.4 (pinned source HTTP 404) => must become acp:omp alone
    "msg": "/tmp/cf-homeA/.omp/agent/config.yml, /tmp/cf-homeA/.omp/agent/lsp.json, /tmp/cf-homeA/.no-mistakes/config.yaml, /tmp/cf-homeA/.acpx/config.json\nalready exist and were not rewritten (first-write-only inputs)."
TASK [Set the no-mistakes pipeline agent from the pi adapter check] ************
TASK [Report why the no-mistakes pipeline agent stays acp:omp] *****************
    "msg": "Not switching the no-mistakes pipeline agent to pi: internal/agent/pi.go is gone from v1.85.4 (HTTP 404); re-prove omp-as-pi against v1.85.4 first. It stays acp:omp until omp-as-pi is re-proven against this release."
TASK [Warn that the no-mistakes pi adapter could not be verified] **************
RUNNING HANDLER [reload user systemd] ******************************************
    "msg": "HANDLER reload user systemd fired"
RUNNING HANDLER [no-mistakes daemon restart required] **************************
    "msg": "HANDLER no-mistakes daemon restart required fired"
PLAY RECAP *********************************************************************
localhost                  : ok=20   changed=2    unreachable=0    failed=0    skipped=4    rescued=0    ignored=0   
--- config.yaml agent after:
agent:
- acp:omp
agent_path_override:
  pi: /tmp/cf-homeA/.no-mistakes/omp-as-pi/omp-as-pi
agent_config:
  pi:
    model: anthropic/claude-sonnet-5-5
    effort: high

##### B: host on [pi, acp:omp]; GitHub unreachable => agent left as-is, warning, no handler
TASK [Set the no-mistakes pipeline agent from the pi adapter check] ************
TASK [Report why the no-mistakes pipeline agent stays acp:omp] *****************
TASK [Warn that the no-mistakes pi adapter could not be verified] **************
    "msg": "WARNING: could not verify the pi adapter of this no-mistakes release: could not fetch internal/agent/pi.go at v1.85.3 to verify it (HTTP 000)\ncould not fetch internal/agent/ompgate.go at v1.85.3 to verify it (HTTP 000)\ncould not fetch internal/agent/fallback.go at v1.85.3 to verify it (HTTP 000)\ncould not fetch internal/agentcfg/pi_profile.go at v1.85.3 to verify it (HTTP 000). The pipeline agent setting is left as it is; the next apply checks again."
RUNNING HANDLER [reload user systemd] ******************************************
    "msg": "HANDLER reload user systemd fired"
RUNNING HANDLER [no-mistakes daemon restart required] **************************
    "msg": "HANDLER no-mistakes daemon restart required fired"
PLAY RECAP *********************************************************************
localhost                  : ok=19   changed=1    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0   
--- config.yaml diff before/after:
(identical)

##### C: operator chose agent: [claude] => untouched even when adapter verifies
RUNNING HANDLER [reload user systemd] ******************************************
    "msg": "HANDLER reload user systemd fired"
RUNNING HANDLER [no-mistakes daemon restart required] **************************
    "msg": "HANDLER no-mistakes daemon restart required fired"
PLAY RECAP *********************************************************************
localhost                  : ok=18   changed=1    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
--- config.yaml after:
agent: [claude]
##### B rerun (drop-in path from the copy now corrected): inconclusive fetch, expect changed=0 and no restart handler
PLAY RECAP *********************************************************************
localhost                  : ok=17   changed=0    unreachable=0    failed=0    skipped=5    rescued=0    ignored=0   
##### C rerun: operator agent, expect changed=0
PLAY RECAP *********************************************************************
localhost                  : ok=16   changed=0    unreachable=0    failed=0    skipped=6    rescued=0    ignored=0   
agent: [claude]
Evidence: omp-as-pi preflight and refusals with the real omp
\### omp-as-pi --omp-as-pi-check with the real omp
omp-as-pi: ok (omp omp/18.4.8, overlay pinned)
exit=0
\### refusal: unknown flag via real wrapper (fail closed, exit 64)
omp-as-pi: refusing unrecognized argument '--bogus': the no-mistakes pi adapter changed, so omp-as-pi must be updated before it can be trusted
exit=64
\### refusal: tampered overlay
omp-as-pi: gate overlay /tmp/cf-ov-7PwE/gate-overlay.yml does not match its pinned sha256; refusing an unproven neutralization
exit=64
Evidence: omp-as-pi wrapper driving real omp: cold step, resume, gate neutralization, refusals
\### 1. cold step: --mode json --model <m> -nc, prompt on stdin (what the pi adapter sends first)
exit=
session id from stream header: 01a0f6c8-38fd-712e-917b-0925b5d87ed8
reply: ok
events: advisor_cost_changed,agent_end,agent_start,message_end,message_start,message_update,session,turn_end,turn_start

\### 2. resume: --mode json --session 01a0f6c8-38fd-712e-917b-0925b5d87ed8 --model <m> -nc (review-fix round reuses the session)
session id after resume: 01a0f6c8-38fd-712e-917b-0925b5d87ed8  (same session: yes)
reply: ok
events: advisor_cost_changed,agent_end,agent_start,message_end,message_start,message_update,session,turn_end,turn_start
model requests: 2
user turns the model saw on the resumed request: ['second\n']

\### 3. gate neutralization: no AGENTS.md/CLAUDE.md content reached the model
with -nc (gate): project AGENTS.md canary reached the model: False
without -nc (control): project AGENTS.md canary reached the model: True

\### 4. adversarial: unknown flag and --session=<uuid> spelling are refused with exit 64
omp-as-pi: refusing unrecognized argument '--bogus': the no-mistakes pi adapter changed, so omp-as-pi must be updated before it can be trusted
exit=64
omp-as-pi: refusing unrecognized argument '--session=01a0f6c8-38fd-712e-917b-0925b5d87ed8': the no-mistakes pi adapter changed, so omp-as-pi must be updated before it can be trusted
exit=64
cold step session: 01a0f6c9-3571-7443-a430-8b8e1d4e21c6
requests: 2 messages per request: [2, 4]
resumed request still carries the first step's prompt (BLUEHERON): True
Evidence: Pattern-kill guard in real omp sessions (sentinel survives, control kills, step 4 Blocked results)
\### 1. guard installed by agents.yml in the disposable HOME; pattern kill must be blocked and the sentinel must survive
Working...
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this host shares one uid, so the pattern can match other workers (their briefs sit in their argv). Kill only PIDs you started: `cmd & pid=$!`, then `kill "$pid"`, or `kill` a PID you verified under your own process tree.
SENTINEL STILL ALIVE (pid 3093773) => blocked

\### 2. control: same prompt with --no-extensions; the pattern kill really runs
Working...
TOOL RESULT: pkill: refusing to signal pid 3095126 (this shell or one of its ancestors)


Wall time: 0.11 seconds
SENTINEL KILLED (control: the guard is what blocks)

\### 3. allowed: kill by a verified PID
Working...
TOOL RESULT: killed-by-pid


Wall time: 0.11 seconds
SENTINEL KILLED by PID (allowed)

\### 4. adversarial: kill-by-pgrep substitution and a pkill inside a pipeline/wrapper
Working...
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this host shares one uid, so the pattern can match other workers (their briefs sit in their argv). Kill only PIDs you started: `cmd & pid=$!`, then `kill "$pid"`, or `kill` a PID you verified under your own process tree.
SENTINEL STILL ALIVE => blocked
Working...
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this host shares one uid, so the pattern can match other workers (their briefs sit in their argv). Kill only PIDs you started: `cmd & pid=$!`, then `kill "$pid"`, or `kill` a PID you verified under your own process tree.
SENTINEL STILL ALIVE => blocked

\### 5. allowed: a plain read-only command that merely mentions the word in an echo
Working...
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this host shares one uid, so the pattern can match other workers (their briefs sit in their argv). Kill only PIDs you started: `cmd & pid=$!`, then `kill "$pid"`, or `kill` a PID you verified under your own process tree.
Evidence: Kill guard over-blocks commands that only mention pkill
\### commands that only mention the word, none of which kills anything
--- RUN: grep -rn pkill /tmp/cf-live/drive_fp.sh
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this 
--- RUN: git log --oneline -1 --grep="pkill guard"
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this 
--- RUN: echo pkill is only mentioned
TOOL RESULT: Blocked: pkill/killall and kill-by-pgrep select processes by name or command line, and every agent on this 
--- RUN: ls ./pkill-notes
TOOL RESULT: ls: cannot access './pkill-notes': No such file or directory
--- RUN: grep -f omp /dev/null
TOOL RESULT: grep: omp: No such file or directory (os error 2)
Evidence: Clean A/B of effective omp settings: seed only vs seed plus daemon overlay, PI_CONFIG_FILES unset
# host shell had PI_CONFIG_FILES=~/.no-mistakes/omp-config.yml (the live daemon overlay); both runs below use env -u PI_CONFIG_FILES and HOME=/tmp/cf-home-ov2 seeded from config/omp.yml

\### A. seed only (no overlay)
  disabledProviders = [] (array)
  modelRoles = {"designer":"anthropic/claude-fable-5-1","smol":"anthropic/claude-sonnet-5:off","slow":"anthropic/claude-fable-5-1:xhigh","vision":"anthropic/claude-opus-5-5:auto","plan":"anthropic/claude-fable-5-1","commit":"anthropic/claude-sonnet-5:off","tiny":"anthropic/claude-sonnet-5:off","advisor":"anthropic/claude-sonnet-5","Kimi":"kimi-code/k3","task":"anthropic/claude-opus-5-5:auto","subagent":"anthropic/claude-opus-5-5:auto","default":"anthropic/claude-opus-5-5:xhigh","memory":"anthropic/claude-opus-5-5:off"} (record)
  memory.backend = off (off|local|hindsight|mnemopi|sharpshooter)
  tier.advisor = none (inherit|none|auto|default|flex|scale|priority|ultrafast)
  providers.anthropic.serverSideFallback = false (boolean)
  advisor.enabled = false (boolean)
  advisor.syncBacklog = 1 (off|1|3|5)
  advisor.immuneTurns = 4 (number)
  advisor.maxNotesPerUpdate = 4 (number)
  advisor.evictStaleResults = true (boolean)

\### B. seed + overlay via PI_CONFIG_FILES=/tmp/cf-home-ov2/.no-mistakes/omp-config.yml
  disabledProviders = [] (array)
  modelRoles = {"default":"anthropic/claude-sonnet-5-5:high","advisor":"anthropic/claude-opus-5-5:medium","designer":"anthropic/claude-fable-5-1","smol":"anthropic/claude-sonnet-5:off","slow":"anthropic/claude-fable-5-1:xhigh","vision":"anthropic/claude-opus-5-5:auto","plan":"anthropic/claude-fable-5-1","commit":"anthropic/claude-sonnet-5:off","tiny":"anthropic/claude-sonnet-5:off","Kimi":"kimi-code/k3","task":"anthropic/claude-opus-5-5:auto","subagent":"anthropic/claude-opus-5-5:auto","memory":"anthropic/claude-opus-5-5:off"} (record)
  memory.backend = off (off|local|hindsight|mnemopi|sharpshooter)
  tier.advisor = none (inherit|none|auto|default|flex|scale|priority|ultrafast)
  providers.anthropic.serverSideFallback = false (boolean)
  advisor.enabled = true (boolean)
  advisor.syncBacklog = 1 (off|1|3|5)
  advisor.immuneTurns = 4 (number)
  advisor.maxNotesPerUpdate = 4 (number)
  advisor.evictStaleResults = true (boolean)

\### diff A -> B
2c2
<   modelRoles = {"designer":"anthropic/claude-fable-5-1","smol":"anthropic/claude-sonnet-5:off","slow":"anthropic/claude-fable-5-1:xhigh","vision":"anthropic/claude-opus-5-5:auto","plan":"anthropic/claude-fable-5-1","commit":"anthropic/claude-sonnet-5:off","tiny":"anthropic/claude-sonnet-5:off","advisor":"anthropic/claude-sonnet-5","Kimi":"kimi-code/k3","task":"anthropic/claude-opus-5-5:auto","subagent":"anthropic/claude-opus-5-5:auto","default":"anthropic/claude-opus-5-5:xhigh","memory":"anthropic/claude-opus-5-5:off"} (record)
---
>   modelRoles = {"default":"anthropic/claude-sonnet-5-5:high","advisor":"anthropic/claude-opus-5-5:medium","designer":"anthropic/claude-fable-5-1","smol":"anthropic/claude-sonnet-5:off","slow":"anthropic/claude-fable-5-1:xhigh","vision":"anthropic/claude-opus-5-5:auto","plan":"anthropic/claude-fable-5-1","commit":"anthropic/claude-sonnet-5:off","tiny":"anthropic/claude-sonnet-5:off","Kimi":"kimi-code/k3","task":"anthropic/claude-opus-5-5:auto","subagent":"anthropic/claude-opus-5-5:auto","memory":"anthropic/claude-opus-5-5:off"} (record)
6c6
<   advisor.enabled = false (boolean)
---
>   advisor.enabled = true (boolean)
Evidence: install_tools.py resolves the no-mistakes prerelease live
\### install_tools.py --resolve --tools gh,no-mistakes,treehouse (agents profile tools), real GitHub
{
 "herdr": {
  "version": "0.9.3",
  "assets": {
   "linux-x86_64": {
    "url": "https://github.com/herdrdev/herdr/releases/download/v0.9.3/herdr-linux-x86_64",
    "sha256": "18a8dc65f1c2fa485884344356dea1cfd911c6f06cf46fa78e193f4087f4dba7",
    "format": "file",
    "binaries": {
     "herdr": "herdr"
    }
   }
  }
 },
 "no-mistakes": {
  "version": "1.85.3",
  "assets": {
   "linux-x86_64": {
    "url": "https://github.com/kunchenguid/no-mistakes/releases/download/v1.85.3/no-mistakes-v1.85.3-linux-amd64.tar.gz",
    "sha256": "cdc0c23407a8f2880762a4502dc4bfc91d93ac49e885d2b556df6cd91beaeba1",
    "format": "tar",
    "binaries": {
     "no-mistakes": "no-mistakes"
    }
   }
  }
 }
}
Evidence: Targeted pytest run
============================= test session starts ==============================
platform linux -- Python 3.13.14, pytest-9.0.2, pluggy-1.6.0 -- /tmp/cf-test-venv/bin/python
rootdir: ~/.no-mistakes/worktrees/381d71ab9820/01M3V5QYKXQE87GY7CCSZ41D1F
configfile: pyproject.toml
collecting ... collected 98 items

tests/test_check_adapter.py::test_a_source_that_differs_from_its_pin_is_a_mismatch PASSED [  1%]
tests/test_check_adapter.py::test_a_pinned_source_that_is_gone_from_the_tag_is_a_mismatch PASSED [  2%]
tests/test_check_adapter.py::test_server_errors_and_network_failures_are_inconclusive[503-503] PASSED [  3%]
tests/test_check_adapter.py::test_server_errors_and_network_failures_are_inconclusive[429-429] PASSED [  4%]
tests/test_check_adapter.py::test_server_errors_and_network_failures_are_inconclusive[neterr-000] PASSED [  5%]
tests/test_check_adapter.py::test_server_errors_and_network_failures_are_inconclusive[cut-000] PASSED [  6%]
tests/test_check_adapter.py::test_a_proven_mismatch_wins_over_an_inconclusive_fetch[503-plan0] PASSED [  7%]
tests/test_check_adapter.py::test_a_proven_mismatch_wins_over_an_inconclusive_fetch[neterr-plan1] PASSED [  8%]
tests/test_check_adapter.py::test_a_proven_mismatch_wins_over_an_inconclusive_fetch[cut-plan2] PASSED [  9%]
tests/test_check_adapter.py::test_the_tag_is_required PASSED             [ 10%]
tests/test_omp_no_pattern_kill.py::test_blocks_pattern_kills[pkill -f 'ponytail-review main'] PASSED [ 11%]
tests/test_omp_no_pattern_kill.py::test_blocks_pattern_kills[sleep 1; killall node] PASSED [ 12%]
tests/test_omp_no_pattern_kill.py::test_blocks_pattern_kills[pgrep -f omp | xargs kill] PASSED [ 13%]
tests/test_omp_no_pattern_kill.py::test_blocks_pattern_kills[kill $(pgrep -f herdr)] PASSED [ 14%]
tests/test_omp_no_pattern_kill.py::test_blocks_pattern_kills[kill `pgrep omp`] PASSED [ 15%]
tests/test_omp_no_pattern_kill.py::test_allows_pid_kills_and_mentions[cmd & pid=$!; kill "$pid"] PASSED [ 16%]
tests/test_omp_no_pattern_kill.py::test_allows_pid_kills_and_mentions[kill 12345] PASSED [ 17%]
tests/test_omp_no_pattern_kill.py::test_allows_pid_kills_and_mentions[pgrep -f omp] PASSED [ 18%]
tests/test_omp_no_pattern_kill.py::test_allows_pid_kills_and_mentions[ls ./pkill-notes] PASSED [ 19%]
tests/test_omp_no_pattern_kill.py::test_ignores_other_tools PASSED       [ 20%]
tests/test_install_tools.py::test_verified_install_runs_and_second_install_changes_nothing PASSED [ 21%]
tests/test_install_tools.py::test_bad_checksum_never_installs_command PASSED [ 22%]
tests/test_install_tools.py::test_unmanaged_command_is_preserved PASSED  [ 23%]
tests/test_install_tools.py::test_local_binary_drift_is_not_silently_accepted PASSED [ 24%]
tests/test_install_tools.py::test_archive_traversal_cannot_escape_staging[tar] PASSED [ 25%]
tests/test_install_tools.py::test_archive_traversal_cannot_escape_staging[zip] PASSED [ 26%]
tests/test_install_tools.py::test_download_rejects_plain_http PASSED     [ 27%]
tests/test_install_tools.py::test_latest_releases_are_pinned_to_the_digests_their_publishers_list PASSED [ 28%]
tests/test_install_tools.py::test_no_mistakes_follows_the_prerelease_channel_and_skips_drafts PASSED [ 29%]
tests/test_install_tools.py::test_a_release_list_with_only_drafts_is_refused PASSED [ 30%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[herdrdev/herdr] PASSED [ 31%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[oven-sh/bun] PASSED [ 32%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[cli/cli] PASSED [ 33%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[kunchenguid/no-mistakes] PASSED [ 34%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[kunchenguid/treehouse] PASSED [ 35%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[astral-sh/uv] PASSED [ 36%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[h4ckf0r0day/obscura] PASSED [ 37%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[node] PASSED [ 38%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[rustup-init] PASSED [ 39%]
tests/test_install_tools.py::test_release_without_a_published_checksum_is_refused[psutil] PASSED [ 40%]
tests/test_install_tools.py::test_github_token_goes_only_to_the_github_api[env-token-Bearer env-token] PASSED [ 41%]
tests/test_install_tools.py::test_github_token_goes_only_to_the_github_api[-None] PASSED [ 42%]
tests/test_install_tools.py::test_resolve_covers_only_the_requested_tools PASSED [ 43%]
tests/test_install_tools.py::test_resolve_selection_matches_what_the_flags_install[argv0-expected0] PASSED [ 44%]
tests/test_install_tools.py::test_resolve_selection_matches_what_the_flags_install[argv1-expected1] PASSED [ 45%]
tests/test_install_tools.py::test_resolve_selection_matches_what_the_flags_install[argv2-expected2] PASSED [ 46%]
tests/test_install_tools.py::test_resolve_selection_matches_what_the_flags_install[argv3-expected3] PASSED [ 47%]
tests/test_install_tools.py::test_resolve_refuses_a_source_that_does_not_exist PASSED [ 48%]
tests/test_install_tools.py::test_the_dropped_npm_set_loses_its_links_but_keeps_the_prefix_and_other_installs PASSED [ 50%]
tests/test_install_tools.py::test_omp_plugins_are_managed_from_the_target_home_whatever_the_installer_cwd PASSED [ 51%]
tests/test_install_tools.py::test_current_follows_the_newest_release_and_a_repeat_changes_nothing PASSED [ 52%]
tests/test_install_tools.py::test_an_apply_merges_its_resolved_record_into_the_existing_one[argv0-expected0] PASSED [ 53%]
tests/test_install_tools.py::test_an_apply_merges_its_resolved_record_into_the_existing_one[argv1-expected1] PASSED [ 54%]
tests/test_install_tools.py::test_a_failed_command_reports_why_it_failed PASSED [ 55%]
tests/test_configuration.py::test_valid_configuration_is_accepted_by_real_schema PASSED [ 56%]
tests/test_configuration.py::test_legacy_obscura_keys_are_ignored_with_one_warning_and_apply_proceeds PASSED [ 57%]
tests/test_configuration.py::test_workspace_cannot_escape_operator_home[~/Dev] PASSED [ 58%]
tests/test_configuration.py::test_workspace_cannot_escape_operator_home[~/../other/Dev] PASSED [ 59%]
tests/test_configuration.py::test_workspace_cannot_escape_operator_home[~] PASSED [ 60%]
tests/test_configuration.py::test_invalid_secret_field_is_rejected_without_echoing_value PASSED [ 61%]
tests/test_configuration.py::test_firstmate_cannot_silently_omit_its_agent_dependencies PASSED [ 62%]
tests/test_configuration.py::test_firstmate_revision_pin_is_rejected PASSED [ 63%]
tests/test_configuration.py::test_fleet_guards_require_docker_and_firstmate PASSED [ 64%]
tests/test_configuration.py::test_fleet_guards_accept_the_default_document_when_enabled PASSED [ 65%]
tests/test_configuration.py::test_browsers_valid_block_accepted PASSED   [ 66%]
tests/test_configuration.py::test_fleet_fixture_archive_cannot_traverse PASSED [ 67%]
tests/test_configuration.py::test_unknown_fleet_field_is_rejected PASSED [ 68%]
tests/test_configuration.py::test_bad_polling_window_cannot_disable_idle_accrual PASSED [ 69%]
tests/test_configuration.py::test_init_preserves_existing_local_configuration PASSED [ 70%]
tests/test_configuration.py::test_root_operator_is_rejected_before_config_is_written PASSED [ 71%]
tests/test_configuration.py::test_questions_launch_only_on_an_interactive_terminal_outside_ci[False--False] PASSED [ 72%]
tests/test_configuration.py::test_questions_launch_only_on_an_interactive_terminal_outside_ci[True-true-False] PASSED [ 73%]
tests/test_configuration.py::test_questions_launch_only_on_an_interactive_terminal_outside_ci[True--True] PASSED [ 74%]
tests/test_configuration.py::test_second_apply_does_not_reopen_the_questions PASSED [ 75%]
tests/test_configuration.py::test_questions_wait_for_an_omp_sign_in PASSED [ 76%]
tests/test_configuration.py::test_another_accounts_apply_skips_questions_without_reading_the_unreadable_home PASSED [ 77%]
tests/test_configuration.py::test_apply_warns_when_firstmate_still_tracks_the_stale_fork PASSED [ 78%]
tests/test_configuration.py::test_verify_fails_when_firstmate_origin_is_not_the_configured_url PASSED [ 79%]
tests/test_configuration.py::test_verify_passes_when_firstmate_origin_is_the_configured_url PASSED [ 80%]
tests/test_configuration.py::test_existing_omp_config_gets_the_status_row_keys_once PASSED [ 81%]
tests/test_configuration.py::test_omp_config_that_has_the_status_row_keys_is_left_alone PASSED [ 82%]
tests/test_configuration.py::test_omp_config_without_the_custom_preset_is_left_alone[theme: {dark: titanium}\n] PASSED [ 83%]
tests/test_configuration.py::test_omp_config_without_the_custom_preset_is_left_alone[statusLine:\n  preset: default\n  showHookStatus: true\n] PASSED [ 84%]
tests/test_configuration.py::test_omp_config_without_the_custom_preset_is_left_alone[statusLine:\n  leftSegments: [path, git]\n  showHookStatus: true\n] PASSED [ 85%]
tests/test_configuration.py::test_absent_omp_config_is_not_created_by_the_status_row_step PASSED [ 86%]
tests/test_configuration.py::test_acpx_config_that_still_defaults_to_codex_gets_omp_once PASSED [ 87%]
tests/test_configuration.py::test_acpx_config_with_another_default_is_left_alone[{"defaultAgent": "claude"}\n] PASSED [ 88%]
tests/test_configuration.py::test_acpx_config_with_another_default_is_left_alone[{"ttl": 300}\n] PASSED [ 89%]
tests/test_configuration.py::test_seeded_acpx_config_defaults_to_omp_and_absent_one_is_not_created PASSED [ 90%]
tests/test_configuration.py::test_verified_pi_adapter_moves_the_seeded_agent_to_pi_once PASSED [ 91%]
tests/test_configuration.py::test_a_host_already_on_pi_moves_from_xhigh_to_high_once PASSED [ 92%]
tests/test_configuration.py::test_a_pin_mismatch_moves_the_agent_to_acp_omp_alone PASSED [ 93%]
tests/test_configuration.py::test_an_inconclusive_adapter_check_leaves_the_agent_setting_alone[2] PASSED [ 94%]
tests/test_configuration.py::test_an_inconclusive_adapter_check_leaves_the_agent_setting_alone[127] PASSED [ 95%]
tests/test_configuration.py::test_operator_chosen_agent_is_left_alone PASSED [ 96%]
tests/test_configuration.py::test_the_managed_environment_never_carries_a_process_wide_runtime_limit[False] PASSED [ 97%]
tests/test_configuration.py::test_the_managed_environment_never_carries_a_process_wide_runtime_limit[True] PASSED [ 98%]
tests/test_configuration.py::test_a_new_chrome_devtools_mcp_release_leaves_the_managed_environment_unchanged PASSED [100%]

============================= 98 passed in 58.08s ==============================
Evidence: omp-as-pi offline test suite
ok   cold step
ok   new durable session
ok   resume maps --session to --resume
ok   model/effort/provider pass through
ok   --no-context-files applies the gate neutralization
ok   -nc spelling + resume
ok   refuses: unknown flag
ok   refuses: unknown positional
ok   refuses: missing --mode
ok   refuses: --mode text
ok   refuses: non-UUID session
ok   refuses: partial session id
ok   refuses: two session flags
ok   refuses: flag missing value
ok   refuses: --mode=json spelling
ok   refuses: --session=<uuid> spelling
ok   refuses: --model=<id> spelling
ok   refuses: --thinking=<level> spelling
ok   refuses: --provider=<id> spelling
ok   refuses: tampered gate overlay
ok   refuses: omp without --resume (preflight)
ok   preflight re-runs when the omp binary changes
ok   --omp-as-pi-check
omp-as-pi tests: PASS
Evidence: Default config validation and no-nvim check
\### ./factory validate --config config/default.yml
Valid host configuration: config/default.yml
exit=0
\### tracked files / default-config mentions of neovim
0
0
  • Evidence: Driver scripts (mock model server, ansible harness, omp drivers) (local file: ~/.no-mistakes/evidence/01M3V5QYKXQE87GY7CCSZ41D1F/drivers)
  • Outcome: ⚠️ 1 info across 1 run (26m7s)

Pipeline

Updates from git push no-mistakes

... (11 earlier update rounds omitted to keep the PR body within GitHub's 65536-char limit; full history is in the run log.)

⚠️ **Review** - 14 issues (11 warnings, 3 infos)

🔧 Fix applied.
14 issues (11 warnings, 3 infos) still open:

  • ⚠️ config/omp-as-pi/switch-when-idle.sh:18 - The adapter-hash check pins pi.go, pi_profile.go and fallback.go but not internal/agent/ompgate.go. ompgate.go defines the gate neutralization that omp-as-pi hard-codes: ompGateOverlayYAML, ompGateSuppressionFlags and --config placement. A later no-mistakes release could add a discovery provider or suppression flag there and leave the three pinned files unchanged. --check-adapter would still pass, so agents.yml:170-180 would keep or set agent: [pi, acp:omp]. The wrapper would then run gates with the stale overlay (gate-overlay.yml:1-19, omp-as-pi:123), so repo-supplied instructions could reach a gate agent holding the owner's credentials. I fetched the upstream files at v1.84.0 and v1.85.3. The three existing pins match at both tags, so a fresh apply on either tag passes the check. ompgate.go hashes to 8a598685618334afcb689ea7b1006957855f4c694fc0dcc6447faf086e49aed9 at both tags. Fix: add internal/agent/ompgate.go to PINNED with that hash. Update the 'three source files' wording in docs/dependencies.md and docs/omp.md (step 2). Also fix gate-overlay.yml:1: it claims to be byte-for-byte the upstream constant, but the header comment differs. The YAML body is the same.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:29 - check_adapter runs curl -fsSL with no --connect-timeout or --max-time. ansible/tasks/agents.yml:170-180 runs it on every apply as a plain command task with no timeout, three fetches per apply. A transfer that stalls after connecting makes ./factory apply hang indefinitely before verify. scripts/install_tools.py:108 already bounds its fetches with timeout=60. Fix: add --connect-timeout 10 --max-time 30. A timeout then lands in the existing 'could not fetch' branch.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:29 - check_adapter returns rc 1 both when a pin differs and when curl fails. ansible/tasks/agents.yml:195-213 maps any non-zero rc to agent: [&#39;acp:omp&#39;]. One transient GitHub fetch error therefore downgrades a working [pi, acp:omp] host to cold acp:omp and prints the 'restart the daemon' hint. The next apply flips it back. The intent only calls for acp:omp as the only agent when the adapter 'does not match the pins', and a failed download is not a mismatch. docs/dependencies.md documents the current behaviour as deliberate. Narrower form: give a fetch failure its own exit code and leave the agent list unchanged, while a real mismatch still downgrades. Keeping the live pi agent unverified when the fetch is inconclusive is the policy choice, so the owner needs to decide.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:47 - Simplification: the intent requires an adapter-hash check, and a fresh box gets the pi switch from ansible/tasks/agents.yml on every apply. Lines 47-132 of this script are a host-specific one-shot migration that no intent requirement needs. It waits up to 10h for idle runs, runs no-mistakes update, restarts the daemon, rolls back, and rewrites config.yaml itself. That duplicates the agents.yml transition, and the two definitions diverge. The script rewrites only a literal agent: [acp:omp]\n line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes the anthropic/claude-sonnet-5-5/xhigh pin separately (line 111, agents.yml:~205). Line 94 also writes a dated 'captain order' comment into config.yaml. The script restarts the daemon, while docs/omp.md says the recipe never does. Narrower form: ship only the pins and check_adapter, for example as check-adapter.sh, and drop the migration body. Update docs/omp.md:115 and the 'by hand' paragraph accordingly. No hardening of the migration is proposed.
  • ℹ️ ansible/tasks/agents.yml:195 - The agent rewrite goes from_yaml then to_nice_yaml. The first apply that flips the agent drops every comment from ~/.no-mistakes/config.yaml, including the guidance shipped in config/no-mistakes.yaml and any operator notes. docs/omp.md documents this and keeps the 'never add acp_registry_overrides for omp' rule in the docs. Later applies with an unchanged agent write nothing. This is a deliberate, documented trade-off and not a defect. Preserving comments would need a line-based edit instead of a YAML round-trip.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:30 - Still open from round 1 and not yet decided. Round 1's curl-timeout fix made it more reachable. check_adapter returns 1 both for a real pin mismatch (line 38) and for a failed fetch (line 33). ansible/tasks/agents.yml:203-204 maps any non-zero rc to agent: [&#39;acp:omp&#39;]. The new --connect-timeout 10 --max-time 30 turns a slow GitHub response, which previously hung the apply, into the same downgrade. One transient fetch error or timeout now downgrades a working [pi, acp:omp] host to cold acp:omp and prints the daemon-restart reminder. The next apply flips it back and prints it again. The intent only calls for acp:omp alone when the adapter 'does not match the pins', and an unreachable download is not a mismatch. docs/dependencies.md documents the current behaviour as deliberate. Narrower form: give fetch failure its own exit code and leave the agent list unchanged, while a real mismatch still downgrades. Whether an unverified pi agent may stay live on an inconclusive fetch is the owner's policy call.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:48 - Simplification, still open from round 1 and not yet decided. The intent requires the adapter-hash check. A fresh box already gets the pi switch from ansible/tasks/agents.yml on every apply. Lines 48-133 of this script are a host-specific one-shot migration that no intent requirement needs. It waits up to 10h for idle runs and runs no-mistakes update. It also restarts the daemon, rolls back, and rewrites config.yaml itself. That duplicates the agents.yml transition, and the two diverge. The script rewrites only a literal agent: [acp:omp] line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes the anthropic/claude-sonnet-5-5/xhigh pin separately (line 112 here, agents.yml:202). It writes a dated 'captain order' comment into config.yaml at line 95. It restarts the daemon, although docs/omp.md says the recipe never does. Narrower form that satisfies the intent: ship only the PINNED table and check_adapter, for example as check-adapter.sh. Drop the migration body, and update docs/omp.md and the 'by hand' paragraph. The remedy is removal, not hardening.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:30 - Open from round 1, awaiting the owner. check_adapter returns 1 both for a real pin mismatch (line 38) and for a failed or timed-out fetch (line 33). ansible/tasks/agents.yml:203-204 maps any non-zero rc to agent: [&#39;acp:omp&#39;]. One transient GitHub error therefore downgrades a working [pi, acp:omp] host to cold acp:omp and prints the daemon-restart reminder. The next apply flips it back and prints it again. The intent calls for acp:omp alone only when the adapter 'does not match the pins', and an unreachable download is not a mismatch. docs/dependencies.md documents this as deliberate. Narrower form: give fetch failure its own exit code and leave the agent list unchanged on it, while a real mismatch still downgrades. Owner call: may an unverified pi agent stay live on an inconclusive fetch?
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:48 - Simplification, open from round 1, awaiting the owner. The intent requires the adapter-hash check, and a fresh box already gets the pi switch from ansible/tasks/agents.yml on every apply. Lines 48-133 are a host-specific one-shot migration that no intent requirement needs. It waits up to 10h for idle runs, runs no-mistakes update, restarts the daemon, rolls back, and rewrites config.yaml itself. That duplicates the agents.yml transition, and the two diverge. The script rewrites only a literal agent: [acp:omp] line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes the anthropic/claude-sonnet-5-5/xhigh pin separately (line 112, agents.yml:202). It writes a dated 'captain order' comment into config.yaml. It restarts the daemon, although docs/omp.md says the recipe never does. Narrower form that satisfies the intent: ship only PINNED and check_adapter, for example as check-adapter.sh. Drop the migration body and update docs/omp.md and the 'by hand' paragraph. Remedy is removal.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:30 - check_adapter returns 1 both for a real pin mismatch (line 38) and for a failed or timed-out fetch (lines 30-34). ansible/tasks/agents.yml:203-204 maps any non-zero rc to agent: [&#39;acp:omp&#39;]. One transient GitHub error or the 30 s --max-time therefore downgrades a working [pi, acp:omp] host to cold acp:omp and prints the daemon-restart reminder. The next apply flips it back and prints the reminder again. The intent calls for acp:omp alone only when the adapter 'does not match the pins', and an unreachable download is not a mismatch. docs/dependencies.md documents the current behaviour as deliberate. Narrower form: give a fetch failure its own exit code and leave the agent list unchanged on it, while a real mismatch still downgrades. Owner call: may an unverified pi agent stay live when the fetch is inconclusive?
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:48 - Simplification. The intent requires the adapter-hash check, and a fresh box already gets the pi switch from ansible/tasks/agents.yml on every apply. Lines 48-133 are a host-specific one-shot migration that no intent requirement needs. It waits up to 10 h for idle runs, runs no-mistakes update, restarts the daemon, rolls back, and rewrites config.yaml itself. That duplicates the agents.yml transition, and the two diverge. The script rewrites only a literal agent: [acp:omp] line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes the anthropic/claude-sonnet-5-5/xhigh pin separately (line 112 here, agents.yml:202). Line 95 writes a dated 'captain order' comment into config.yaml. It restarts the daemon, although docs/omp.md says the recipe never does. Narrower form that satisfies the intent: ship only PINNED and check_adapter, for example as check-adapter.sh. Drop the migration body, and update docs/omp.md and its 'by hand' paragraph. The remedy is removal, not hardening.
  • ⚠️ config/omp-as-pi/switch-when-idle.sh:33 - Round 4's exit-code split (the fix for fetch-failure-downgrades-agent) puts every curl failure into the inconclusive bucket, exit 2. curl -fsSL exits 22 for any HTTP status of 400 or above, so a 404 looks the same as a timeout or a 5xx. A 404 on a tag that exists means the pinned source file is gone at that release. Concrete sequence: a later no-mistakes release renames or deletes internal/agent/ompgate.go, pi_profile.go, pi.go or fallback.go, which is a likely sign that the adapter changed. The check fetches that path and gets 404. It prints 'could not fetch … to verify it' and returns 2, and a real mismatch elsewhere cannot rescue it. ansible/tasks/agents.yml:205-206 then keeps current for any rc other than 0 or 1, so a host already on [pi, acp:omp] stays on pi against an adapter that has moved. The wrapper refuses unknown argv itself. It cannot see a change to the omp gate neutralization in ompgate.go, because it applies its own pinned overlay. The author's intent says the host should be on acp:omp alone when the adapter 'does not match the pins'. Test gap: tests/test_switch_when_idle.py:12 and :16 stub only exit 22 (labelled 'fail') and exit 28. No case distinguishes a 404 from a network failure, so the suite passes with a 404 treated as inconclusive. Other places that state the same treatment of HTTP errors: docs/omp.md step 2 ('2 when a source could not be fetched'), docs/dependencies.md (a failed download leaves the agent setting as it is), and the header comment at switch-when-idle.sh:7-9 and :26-29. Narrower form: read the HTTP status (for example curl -w &#39;%{http_code}&#39;), return 1 on 404, and keep 2 for timeouts, DNS or connection errors, 429 and 5xx. Add a 404 stub case to the test. This is the exact boundary the owner decided in round 4 between a proven mismatch and an inconclusive check, so the owner should say whether a missing pinned file counts as proof.
  • ℹ️ config/omp-as-pi/check-adapter.sh:19 - The script pins internal/agentcfg/pi_profile.go. The recorded instruction says internal/agent/pi_profile.go. The script is right and the instruction's path is wrong. I fetched both paths at v1.85.3 on raw.githubusercontent.com. internal/agentcfg/pi_profile.go returns 200 and sha256 be326b6b756e1075d5bf827cec6e80069cd293a79a751ecc7e6dbf0650d490f0, which equals the pin. internal/agent/pi_profile.go returns 404. The other three pins (pi.go, fallback.go, ompgate.go) also return 200 and match. Pinning the instruction's path would make every apply hit the 404-means-mismatch branch, exit 1, and move the host to acp:omp alone. The offline tests cannot catch a wrong directory, because the stub curl matches only the file's basename. Real fetches against v1.85.3 already confirm the current path. docs/omp.md and docs/dependencies.md name the file only as pi_profile.go, so nothing in the docs contradicts it. No code change is needed.
  • ℹ️ ansible/tasks/agents.yml:203 - When the adapter check exits 0 and the host agent is [acp:omp] or [pi, acp:omp], the recursive combine overwrites agent_path_override.pi and agent_config.pi.model and .effort on every apply. It does not keep an operator's own model or effort. A host already on pi at xhigh moves to high once, and the next apply writes nothing. I traced test_a_host_already_on_pi_moves_from_xhigh_to_high_once: the first run changes the config and the second run changes nothing, so it passes. The .pytest_cache/lastfailed entry is from 08:57:24, before agents.yml's last edit at 08:57:25. It is stale and does not show a current failure. docs/omp.md step 3 documents the overwrite, and the operator's high decision requires it. The comment at agents.yml:194-195 says only the agent lists are managed, which understates this. Fixing that wording is optional.
⚠️ **Test** - 1 info
  • ℹ️ config/omp-no-pattern-kill.ts:22 - The guard matches the bare word pkill or killall followed by whitespace anywhere in the bash command text. Driven live in a real omp session, it blocked commands that only mention the word and kill nothing: grep -rn pkill &lt;file&gt;, git log --grep=&#34;pkill guard&#34; and echo pkill is only mentioned. ls ./pkill-notes and grep -f omp were allowed. This fail-closed behaviour is consistent with the BLOCKED/ALLOWED tests, and the block message tells the agent what to do. Quoting the word as &#39;pkill&#39; avoids it. Agents in every omp session cannot grep for or quote the word without a workaround. Evidence: omp-kill-guard-mention-probe.log.
  • Live validation: ✅ go - 9 of 12 scenarios driven live against the product
Scenario Result Live Evidence
Adapter check passes when the installed no-mistakes release still matches the pins (check-adapter.sh against real GitHub) ✅ pass live check-adapter-live.log: v1.85.3, v1.85.2, v1.85.0, v1.84.0 and v1.83.2 each print 'pi adapter sources at <tag> match the pins' and exit 0
Adapter check separates a proven mismatch from an inconclusive fetch ✅ pass live check-adapter-live.log: altered pin against real v1.85.3 bytes gives 'changed in' and exit 1; v1.85.4 and a nonexistent tag give HTTP 404 and exit 1; an unreachable network gives HTTP 000 and exit 2;…
Fresh-box apply installs omp-as-pi, the daemon overlay, the systemd drop-in and the kill-guard extension, and moves the pipeline agent to [pi, acp:omp] in the same apply ✅ pass live ansible-agents-apply1.log and ansible-agents-state-and-apply2.log: config.yaml ends with agent [pi, acp:omp], agent_path_override.pi pointing at the installed wrapper, and sonnet-5-5 at effort high. A…
Adapter mismatch drops the agent to acp:omp alone; an inconclusive check or an operator-chosen agent is left alone ✅ pass live ansible-agents-scenarios-ABC.log: a pi host with release 1.85.4 becomes ['acp:omp'] and reports why. An unreachable GitHub leaves config.yaml byte-identical with a WARNING, and a rerun gives changed=0…
omp-as-pi with the real omp: preflight, cold step then resume of the same session, gate neutralization, fail-closed refusals ✅ pass live omp-as-pi-real-omp.log and omp-as-pi-wrapper-live.log: preflight ok on omp 18.4.8. The cold step emits a session id and the resume keeps that id, with the first prompt still in the resumed request's h…
Daemon-only omp settings overlay changes the default role to sonnet-5-5:high and turns on the Opus advisor, relative to the seeded omp config ✅ pass live omp-overlay-effective-config.log, run with PI_CONFIG_FILES unset and HOME seeded from config/omp.yml. The seed alone gives default opus-5-5:xhigh, advisor sonnet-5 and advisor.enabled=false. Adding th…
Kill guard extension blocks pkill, killall and kill-by-pgrep in a real omp session but allows kills by PID ✅ pass live omp-kill-guard-live.log: the pkill -f sentinel survives and the tool result is the 'Blocked:' reason. With --no-extensions the same pkill kills the sentinel. kill <pid> runs and kills it. In step 4, b…
Installer resolves no-mistakes from the prerelease channel, so the adapter check runs against the version actually installed ✅ pass live install-tools-resolve-live.log: with GitHub's latest-stable endpoint at 1.84.0 and 1.85.3 published as a prerelease, --resolve returns 1.85.3 with its SHA-256
Code-Factory ships no nvim config and the default configuration still validates ✅ pass live validate-and-no-nvim.log: factory validate prints 'Valid host configuration', exit 0; zero tracked nvim/neovim/lazyvim files or mentions; the disposable-HOME apply created no nvim files
Targeted existing tests: check-adapter, kill-guard, install_tools, configuration and the omp-as-pi offline suite ⏸️ untested no The prior payload ran these only as pytest (98 passed) and the omp-as-pi offline suite (PASS), which use stub curl and stub omp. It recorded live=false, so it did not establish a live result for this…
The real no-mistakes daemon loads the systemd drop-in, so daemon-spawned omp reads the overlay ⏸️ untested no Starting or restarting the real no-mistakes daemon would kill pipeline runs in flight on this host, and the workspace boundary forbids changing the host's user systemd units. I did not build a disposa…
Personal material is synced to the private vps-setup repo ⏸️ untested no vps-setup is a separate private repository outside this worktree. I have no access to it and no credentials for it, and this change contains none of its content, so there is nothing in this run to dri…
  • pytest tests/test_check_adapter.py tests/test_omp_no_pattern_kill.py tests/test_install_tools.py tests/test_configuration.py (98 passed)
  • bash config/omp-as-pi/test.sh (offline suite, PASS)
  • bash config/omp-as-pi/check-adapter.sh &lt;tag&gt; against real raw.githubusercontent.com for v1.85.3, v1.85.2, v1.85.0, v1.84.0, v1.83.2, v1.85.4 (HTTP 404), a nonexistent tag, no argument, and an unreachable network
  • check-adapter.sh copy with one altered pin, run against the real v1.85.3 bytes (real SHA mismatch)
  • real ansible-playbook run of ansible/tasks/agents.yml against a disposable HOME: fresh apply, second apply, a pin mismatch, an unreachable network, and an operator-chosen agent
  • python3 scripts/install_tools.py --resolve against the real GitHub API (no-mistakes resolves to the prerelease 1.85.3)
  • omp-as-pi --omp-as-pi-check and tampered-overlay and unknown-flag refusals with the real omp 18.4.8
  • installed omp-as-pi driving the real omp against a local mock OpenAI-compatible model: cold step, resume with history carried over, gate neutralization canary, --session=&lt;uuid&gt; refusal
  • real omp sessions loading fm-no-pattern-kill.ts from the disposable HOME: pattern kills blocked with the sentinel surviving, a --no-extensions control where the kill runs, PID kill allowed, and step 4's kill $(pgrep ...) and xargs/sh -c pkill forms returned 'Blocked:' tool results
  • env -u PI_CONFIG_FILES omp config list with HOME seeded from config/omp.yml, with and without the overlay (A/B diff)
  • scripts/factory.py validate --config config/default.yml, a check that no nvim/neovim/lazyvim file or text is tracked, and ansible-playbook site.yml --syntax-check
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

Lean review verdict

ponytail-review against the branch base f718774 (one round, before the pipeline): exit 2 with five findings. What happened to each:

  • Preflight cache in omp-as-pi: kept. The wrapper is the version proven live on the source host, and test.sh covers the cache.
  • switch-when-idle.sh one-shot migration flow: cut later in this PR. Only check-adapter.sh ships.
  • OMP_AS_PI_REFUSALS override: kept. test.sh uses it to keep refusal logs out of the checkout.
  • "Report why it stays acp:omp" task: kept. It is the only place an apply says why pi was not enabled.
  • Python reply parser in test.sh --live: kept. The live suite drives real omp and was not re-proven here.

…ills

Install omp-as-pi and switch the no-mistakes pipeline agent to [pi, acp:omp]
when the installed release's pi adapter sources match the pinned hashes;
otherwise keep acp:omp and report why. Daemon-spawned omp loads an advisor
overlay through a PI_CONFIG_FILES systemd drop-in, and every omp session gets
an extension that blocks process kills selected by name or pattern.
@undeemed
undeemed merged commit cdd9549 into main Oct 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant