fix(coganchor): keep an agent's own runtime local, and read a command as a command - #32
Merged
Conversation
Every entry of an intercepted argv was read with PATH_MAX as its ceiling. An argv entry is not a path: the kernel allows MAX_ARG_STRLEN, and a shell command is routinely longer than four kilobytes. Codex prefixes every `bash -lc` it runs with a preamble well past that, so what reached the target was the first 4096 bytes of the command -- which parses, runs, and means something else. It failed as a syntax error inside a truncated brace, and Codex went on reporting each tool call as done while nothing at all happened on the target. Truncating is worse than not reading: a prefix of a command is a different command. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two ways the agent itself ended up running on the target, which is the one thing the anchor exists to prevent. An `#!/usr/bin/env node` line kept only the `env` here. `env` then searches PATH for the interpreter, one execve per directory, and the first candidate names a path that does not exist here -- not the agent's own by name, so it was sent to the target, where the name resolves. Kimi's whole agent process ran there: it read the target's HOME, found none of its own configuration, and reported the model it was started with as unconfigured. So the whole search is claimed now rather than the one directory the interpreter is really in; a candidate kept here fails here, with the ENOENT that makes `env` try the next. And a program the agent keeps inside its own state directory was answered from this machine as a path but not as a program. grok installs its native binary under `~/.grok/bin` and re-execs it, so grok too ran on the target, where it reported `Not signed in`. Codex was spared both only because its runtime is listed by hand. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`docs/features/concurrency` says how wide a flow runs is a question about the machine and that nothing caps it. This measures the machine: real hmz, real coganchor, the five agent CLIs as installed, against a mock controlled end and a stand-in model provider that scripts the same three-command turn for every backend. Both stand-ins run outside the cgroup, since in production neither is part of the budget being sized, and the rig records the target's own CPU so a run can say whether the ceiling it found was its own. In 16 logical CPUs and 64 GiB: 16-24 agents run at full speed, 32-128 give the most turns per hour, and everything is still correct into the hundreds -- 640 for claude and 192 for kimi, both stopped by memory; 256 for codex, stopped by its own app-server; grok and dsh never stopped inside the rig's 832 slots. The first ceiling anybody meets is neither: hmz holds about three descriptors per concurrent agent, so a stock 1024 soft RLIMIT_NOFILE stops every backend at about 320. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`command -v kimi` answers nothing in a shell that has not been given `~/.local/agents/bin`, and `dirname ""` is `.`, so the rig went looking for `./kimi` and reported the provider import as failed. $AGENT_BIN says where they are outright, the home default stands in, and a missing one is now said plainly rather than three lines later. Also ignores the `lab/` the rig writes beside itself. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
main added the `cursor` profile — twelve known CLIs — while this branch rewrote the same two paragraphs of `docs/guide/remote-execution.md` and `docs/reference/remote-execution.md` for the `env` interpreter search and for programs an agent runs from inside its own state directory. Kept main's roster and this branch's wording in both.
The rig's three scripts had never been through `ruff check`: `bench/` does not exist on main, so nothing linted them where they were written. Fixed rather than ignored, because each was a real thing to say: - `standin_model.py` picked its final text with `FINAL if ... else FINAL`, a conditional whose test was the enclosing `if` and whose arms were the same value. Inlined. - `_fill` narrowed a schema onto the loop variable it came from, so the declared property and the narrowed dict were one name. Now two. - `ramp.py` swapped `sys.stdout` for a file by hand and put it back at the far end of the body. `contextlib.redirect_stdout` is what the standard library has for that, and it puts stdout back however the block ends. - `%d` formatting, a `try`/`except`/`pass`, and `open()` where the module already holds a `Path`. - Docstrings on the three public names that had none. `INP001` and `T201` are ignored for `bench/**` instead: a script run by path inside the measured cgroup has nothing for an `__init__.py` to do, and what a measurement writes, it prints. Same reason `docs/tapes/stage.py` is already listed.
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.
Two coganchor defects that only show up with a real agent on a real target, plus the rig
that found them.
What was wrong
argventry was read atPATH_MAX. A command is not a path, and the kernel letsone be
MAX_ARG_STRLEN(32 pages). A truncated command is worse than an unread one: whatreaches the target is a prefix, which runs and means something else. Codex prefixes
every shell command with a multi-kilobyte preamble, so this was its ordinary case.
#!/usr/bin/env nodemakesenvwalkPATHoneexecveat a time. The first candidate does not exist here, so it was not theagent's own by name and was sent to the target — where the name does resolve, and the
agent itself ended up running there, reading the target's copy of its state directory and
unable to reach the account it was signed in with. The whole search is now claimed, so a
candidate that fails here fails with the
ENOENTthat makesenvtry the next.native binary under
~/.grok/binand re-execs it. Those directories were already answeredfrom this machine as paths; this makes the same claim about executing them.
The rig
bench/coganchor-concurrency/— realhmz, real coganchor interception, a stand-in modelprovider and a mock controlled end, climbing the concurrency ladder until something stops
behaving.
RESULTS.mdhas the numbers.Merge notes
cursorprofile (twelve known CLIs) while this branch rewrote the same twoparagraphs of the remote-execution guide and reference. Kept main's roster and this
branch's wording.
ruff check—bench/does not exist onmain. Fixed rather than ignored, except
INP001/T201, which are structural to a scriptrun by path, as for
docs/tapes/stage.py.Verification
uv run pre-commit run --all-files— green (ruff check, ruff format, pyright strict).uv run pytest— full suite, locally.summarise.pybyte-identical over the committed ladders, andstandin_model.pyidenticalacross all three wire protocols.
🤖 Generated with Claude Code