feat(config): run omp as the no-mistakes pipeline agent and block pattern kills - #27
Merged
Merged
Conversation
…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.
…solate overlay test
…pter check exits, drop = flag forms
…mistakes, effort high
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
config/omp-as-pi/, a wrapper that lets no-mistakes drive omp through itspiadapter. It includes a sha256-pinned gate overlay,check-adapter.sh(pins the adapter sources of the installed no-mistakes release) and an offlinetest.sh, which CI now runs.ansible/tasks/agents.ymlinstalls 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.ymlalso runs the wrapper preflight.config/no-mistakes-omp.ymloverlay (Opus advisor, Sonnet default) that only daemon-spawned omp loads, through ano-mistakes-daemon-.service.dsystemd drop-in that setsPI_CONFIG_FILES. It also adds theconfig/omp-no-pattern-kill.tsomp extension, installed every apply, which blockspkill,killalland kill-by-pgrepin every omp session. Changing the daemon's omp overlay, drop-in or agent setting prints a manual daemon-restart reminder.scripts/install_tools.pynow resolvesno-mistakesfrom the newest non-draft release, prereleases included.tests/test_check_adapter.py,test_omp_no_pattern_kill.py,test_configuration.py,test_install_tools.pyandcontainer-smoke.share added or updated, and the omp, configuration, dependencies, architecture, capacity and host-move docs are updated.Risk Assessment
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.
Evidence: check-adapter.sh live against GitHub (match, altered pin, 404, unreachable, usage)
Evidence: Fresh-box agents.yml apply (ansible-playbook, disposable HOME)
Evidence: Resulting files, config.yaml, drop-in, and second apply with changed=0
Evidence: Pin mismatch, inconclusive fetch, and operator-chosen agent scenarios
Evidence: omp-as-pi preflight and refusals with the real omp
Evidence: omp-as-pi wrapper driving real omp: cold step, resume, gate neutralization, refusals
Evidence: Pattern-kill guard in real omp sessions (sentinel survives, control kills, step 4 Blocked results)
Evidence: Kill guard over-blocks commands that only mention pkill
Evidence: Clean A/B of effective omp settings: seed only vs seed plus daemon overlay, PI_CONFIG_FILES unset
Evidence: install_tools.py resolves the no-mistakes prerelease live
Evidence: Targeted pytest run
Evidence: omp-as-pi offline test suite
Evidence: Default config validation and no-nvim check
~/.no-mistakes/evidence/01M3V5QYKXQE87GY7CCSZ41D1F/drivers)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.)
🔧 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,ompGateSuppressionFlagsand--configplacement. A later no-mistakes release could add a discovery provider or suppression flag there and leave the three pinned files unchanged.--check-adapterwould still pass, so agents.yml:170-180 would keep or setagent: [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: addinternal/agent/ompgate.goto PINNED with that hash. Update the 'three source files' wording in docs/dependencies.md and docs/omp.md (step 2). Also fixgate-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_adapterrunscurl -fsSLwith no--connect-timeoutor--max-time. ansible/tasks/agents.yml:170-180 runs it on every apply as a plaincommandtask with no timeout, three fetches per apply. A transfer that stalls after connecting makes./factory applyhang indefinitely before verify. scripts/install_tools.py:108 already bounds its fetches withtimeout=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_adapterreturns rc 1 both when a pin differs and when curl fails. ansible/tasks/agents.yml:195-213 maps any non-zero rc toagent: ['acp:omp']. 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, runsno-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 literalagent: [acp:omp]\nline and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes theanthropic/claude-sonnet-5-5/xhighpin 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 andcheck_adapter, for example ascheck-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 goesfrom_yamlthento_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_adapterreturns 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 toagent: ['acp:omp']. The new--connect-timeout 10 --max-time 30turns 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 runsno-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 literalagent: [acp:omp]line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes theanthropic/claude-sonnet-5-5/xhighpin 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 andcheck_adapter, for example ascheck-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_adapterreturns 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 toagent: ['acp:omp']. 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, runsno-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 literalagent: [acp:omp]line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes theanthropic/claude-sonnet-5-5/xhighpin 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 andcheck_adapter, for example ascheck-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_adapterreturns 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 toagent: ['acp:omp']. One transient GitHub error or the 30 s--max-timetherefore 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, runsno-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 literalagent: [acp:omp]line and exits 'unexpected config shape' otherwise, while agents.yml handles any key order. Each hard-codes theanthropic/claude-sonnet-5-5/xhighpin 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 andcheck_adapter, for example ascheck-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 -fsSLexits 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 deletesinternal/agent/ompgate.go,pi_profile.go,pi.goorfallback.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 keepscurrentfor 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 onacp:ompalone when the adapter 'does not match the pins'. Test gap: tests/test_switch_when_idle.py:12 and :16 stub onlyexit 22(labelled 'fail') andexit 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 examplecurl -w '%{http_code}'), 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 pinsinternal/agentcfg/pi_profile.go. The recorded instruction saysinternal/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.goreturns 200 and sha256 be326b6b756e1075d5bf827cec6e80069cd293a79a751ecc7e6dbf0650d490f0, which equals the pin.internal/agent/pi_profile.goreturns 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 aspi_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 recursivecombineoverwritesagent_path_override.piandagent_config.pi.modeland.efforton every apply. It does not keep an operator's own model or effort. A host already on pi atxhighmoves tohighonce, 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/lastfailedentry 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'shighdecision requires it. The comment at agents.yml:194-195 says only the agent lists are managed, which understates this. Fixing that wording is optional.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 <file>,git log --grep="pkill guard"andecho pkill is only mentioned.ls ./pkill-notesandgrep -f ompwere 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'pkill'avoids it. Agents in every omp session cannot grep for or quote the word without a workaround. Evidence: omp-kill-guard-mention-probe.log.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 <tag>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 networkcheck-adapter.shcopy with one altered pin, run against the real v1.85.3 bytes (real SHA mismatch)realansible-playbookrun of ansible/tasks/agents.yml against a disposable HOME: fresh apply, second apply, a pin mismatch, an unreachable network, and an operator-chosen agentpython3 scripts/install_tools.py --resolveagainst the real GitHub API (no-mistakes resolves to the prerelease 1.85.3)omp-as-pi --omp-as-pi-checkand tampered-overlay and unknown-flag refusals with the real omp 18.4.8installed 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=<uuid>refusalreal omp sessions loading fm-no-pattern-kill.ts from the disposable HOME: pattern kills blocked with the sentinel surviving, a--no-extensionscontrol where the kill runs, PID kill allowed, and step 4's kill $(pgrep ...) and xargs/sh -c pkill forms returned 'Blocked:' tool resultsenv -u PI_CONFIG_FILES omp config listwith 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, andansible-playbook site.yml --syntax-check✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.
Lean review verdict
ponytail-reviewagainst the branch basef718774(one round, before the pipeline): exit 2 with five findings. What happened to each:omp-as-pi: kept. The wrapper is the version proven live on the source host, andtest.shcovers the cache.switch-when-idle.shone-shot migration flow: cut later in this PR. Onlycheck-adapter.shships.OMP_AS_PI_REFUSALSoverride: kept.test.shuses it to keep refusal logs out of the checkout.test.sh --live: kept. The live suite drives real omp and was not re-proven here.