Repository navigation
feat: let the owner exempt an agent's jobs - #3
Merged
Merged
Conversation
run and attach record the agent session that starts a job, from --agent or the CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID variable, and restart specs keep it. A job whose agent the owner lists in ignored-agents runs full with the reason agent_ignored, whatever the power and temperature, and follows the policy again once the agent leaves the list. The reason is live-only, so replay, sweeps and the native kernel keep their outputs and protocols.
attach no longer credits the session running it, since someone else started the process it guards; it records an agent only from --agent. For an ignored agent the policy still decides every cycle, so its thermal cooldown is kept and a job taken off the list stays paused until the pack cools, as replay shows; decision events record the policy's own decision under policy. The ignore list is read without blocking on a FIFO, as a regular UTF-8 file of at most 64 KiB with an optional byte order mark, and invalid lines are skipped and reported by line number. A malformed CLAUDE_CODE_SESSION_ID falls through to CODEX_THREAD_ID. Status marks an agent as listed and shows the ignored agents only when the list or agents are in use.
Path.write_text translates newlines on Windows, so the CRLF line became CR CR LF and the invalid entry moved to line 6. Writing the bytes keeps the file exactly as a Windows editor saves it.
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.
Supported path and problem
Coding agents such as Claude Code and Codex are told to run long jobs under train-guard. The owner sometimes wants one agent's jobs to run at full speed whatever the power and temperature, for example a short verification on battery, without turning the policy off for every other job. train-guard had no way to tell which agent started a job.
Behavior before and after
runrecords the agent session that starts a job, from--agent IDor elseCLAUDE_CODE_SESSION_ID, thenCODEX_THREAD_ID, which Claude Code and Codex export to the commands they run.attachrecords an agent only from an explicit--agent, since the session that attaches did not start the job. Restart specifications keep the original agent. A job whose agent the owner lists in<state>/ignored-agentsrunsfullwith the live-only reasonagent_ignored. The policy still decides every cycle, so its thermal cooldown stays true to the pack: once the agent leaves the list, a job whose pack got hot stays paused until it cools totemp_resume_c. The supervisor rereads the list on every cycle, as a regular UTF-8 file (a BOM is accepted) of at most 64 KiB; invalid lines are skipped and reported by line number. An unreadable list exempts no agent, is journaled once per distinct error, and is reported bystatus,listanddoctor.statusshows each guard's agent and the ignored agents; the JSON outputs gainagent,agent_ignoredandignored_agents.The override reason is a separate
OverrideReason, not aDecisionReasonmember: the native kernel numbersDecisionReasonin order and its differential test requires the policy to produce every member.simulateandsweepoutputs are byte-identical tomainfor nominal, compare and bounded runs.Reproduction
Live, on a MacBook on battery with a temporary
TRAIN_GUARD_HOME:run --name probe -- sleep 120from a Claude Code session recorded its session id; the job was suspended (stop,battery_disabled); after listing the id it resumed within one poll withfull/agent_ignored; after removing it, it was suspended again withstop/battery_disabled.Validation that ran
pyteston Python 3.13.7 and 3.9.6 (246 passed),ruff check,ruff format --check,mypy trainguard, branch coverage 90.59%,compileall,build,mkdocs build --strict,git diff --check, the native kernel tests, and the release workflow's source-archive retest and wheel smoke test run locally.Review
An independent review of the first commit found no process-safety defect and two design problems, both fixed in the second commit:
attachcredited the target to the session running it, and the override cleared the thermal cooldown, so a hot pack could resumegentletoo early once the agent left the list.Limits
The exemption changes the workload policy only; macOS thermal protection is unaffected. Jobs started before this change have no agent. Windows was not run locally; CI covers it.