command harness runs a process you own instead of a coding agent Sphynx installs. Sphynx starts it in the sandbox, passes it the case through environment variables, reads events from its stdout, and scores the trial with the case’s validate.
run is the command line, run with bash -c from the workspace. Its install runs once while the sandbox is prepared. The profile directory ships everything else the process needs.
Environment
The profile’s
env is set too. Stdin is closed.
Runner kit
Write the process in TypeScript withsphynx-sh/runner. It reads the environment and prints the event lines for you, and it rejects a line the server would not accept.
run to start the script, for example "run": "bun run agent.ts".
env() returns prompt, model, workspace, home, systemPromptFile and traceLog. It throws when a variable the harness always sets is missing, so a script run by hand fails with the name of the variable.
Every event carries the time it was printed. To collect lines instead of printing them, pass
createEmitter({ write, now }).
Events
The kit prints the lines below. They are also the protocol for a process in any other language. Print one JSON object per line on stdout. Lines with a known_tag are recorded and every other line is ignored, so ordinary logging is fine. at is epoch milliseconds and optional. A line without it is stamped when it is read.
Exit
A non-zero exit does not fail the trial or discard earlier events. If the process never printsFinished, Sphynx records one with the reason exit N. The trial is scored either way.
The process shares the case’s time limit with every other turn of the trial, 15 minutes by default. If it is still running when the limit runs out, it is stopped and the trial ends as timed out.
The recorder
Sphynx sources a small script into every non-interactive bash the process starts, throughBASH_ENV. Its DEBUG trap appends one line per command to SPHYNX_TRACE_LOG. Each line becomes a Command in the journal with a null exit code, unless you already printed a Command with exactly the same text. The log is cleared after each turn, so a turn records only the commands it ran.
It only sees bash and zsh, the two shells with a DEBUG trap:
bash -cand nested bash scripts are traced line by line.sh -con Debian is dash, which has noDEBUGtrap. Only thesh -c …invocation is recorded.- Shells spawned by Node, Python or other runtimes are invisible. A Python agent leaves one line: its own invocation.
- Exit codes are never captured, because the trap fires before the command runs.
Command lines yourself.