Queue Codex and Claude CLI work so it runs after your session expires.
Persistent job scheduling for short-lived agent sessions.
You're deep in a task. You fire off:
claude -p "finish the migration and open a PR"Then one of these happens:
- your session expires mid-run
- your usage quota resets in four hours
- a long task gets cut off
- you want work to keep going after you close the laptop
schedule-agent is for exactly that. Write the prompt now, let it run at 3am, come back to the result. Short-lived sessions stop forcing short-lived workflows.
git clone <repo>
cd schedule-agent
./install.sh
export PATH="$HOME/.local/bin:$PATH" # if not already set
schedule-agentFrom there, the interactive TUI walks you through:
- pick agent (Codex or Claude)
- pick or reuse a session
- write your prompt
- choose when to run (
now + 90 minutes,03:00 tomorrow, etc.) - submit
The install script handles both fresh installs and updates.
schedule-agent is *Linux / nix only. It leans on the system at scheduler — there is no Windows or macOS equivalent baked in.
Minimum set:
| Tool | Why |
|---|---|
| Linux / *nix | relies on at + atd |
| Python 3.10+ | runtime |
at, atrm, atd |
job scheduler |
claude and/or codex |
at least one agent CLI |
nano / nvim / code --wait / any $EDITOR |
for writing prompts |
Verify before installing:
which at atrm claude codex
systemctl status atd # daemon must be runningIf at is missing, install it via your package manager (pacman -S at, apt install at, dnf install at, …) and enable the daemon:
sudo systemctl enable --now atdUnderstanding this before you schedule something is worth thirty seconds.
schedule-agent invokes agents in unattended, permissionless mode — there is no human sitting at the terminal when the job fires, so interactive approval prompts would just hang forever. The defaults are:
codex exec --dangerously-bypass-approvals-and-sandbox "<your prompt>"
claude -p --dangerously-skip-permissions "<your prompt>"These flags are the default prefix applied to every prompt you schedule. Your prompt text is passed as the final argument; everything before it is fixed.
That means a scheduled job may, without asking:
- read, create, modify, or delete files
- run arbitrary shell commands
- make network calls
- change the state of any repository it can reach
Rule of thumb: git status before you schedule. Test the prompt interactively first. Treat scheduled prompts like cron jobs with a language model attached — because that is what they are.
Jobs are handed off to at:
at now + 10 minutesConsequences:
- jobs run non-interactively, with a minimal environment
- jobs do not inherit your full interactive shell (no
direnv, no shell aliases, no.zshrcsugar) - stdin is detached so the scheduled shell wrapper never gets swallowed as input
Prompts are written to disk first, then read back in when the job fires.
Once you've created a job via the TUI, all management is via subcommands.
schedule-agent # interactive TUI
schedule-agent list # all jobs
schedule-agent show <id> # full detail
schedule-agent sessions # discover likely resumable sessions
schedule-agent sessions claude # Claude only
schedule-agent sessions codex # Codex only
schedule-agent sessions --json # machine-readable discovery output
schedule-agent sessions doctor # discovery diagnostics
schedule-agent reschedule <id> "now + 90 minutes"
schedule-agent reschedule <id> "03:00 tomorrow"
schedule-agent set-session <id> <session_id> # attach a session
schedule-agent set-session <id> --new # reset to a fresh session
schedule-agent delete <id>
schedule-agent --dry-run # TUI: creating a job shows preview, skips submission
schedule-agent --dry-run submit <id> # preview the at(1) script for an existing job
schedule-agent edit-prefix {claude|codex} # edit the per-agent prompt prefix in $EDITOR
schedule-agent --versionChange your mind after submitting? Fine. If a job is already queued with at, schedule-agent automatically:
atrms the old submission- rewrites the job definition
- resubmits under the new time / session
No manual bookkeeping.
Session picking and schedule-agent sessions use the same discovery pipeline:
schedule-agentkeeps an append-only local session ledger for jobs it runs- Claude/Codex provider files are scanned as supporting evidence only
- provider files are treated as read-only input and are never rewritten or repaired
- current-project sessions are preferred over unrelated history
- archived, subagent, and other non-resumable sessions stay out of the default picker
When discovery looks wrong, run:
schedule-agent sessions doctor
schedule-agent sessions doctor --jsonPrompt editing resolves in this order:
$SCHEDULE_AGENT_EDITOR$EDITORnano
export SCHEDULE_AGENT_EDITOR="nvim"
# or
export EDITOR="code --wait"| Path | Contents |
|---|---|
~/.local/state/schedule-agent/ |
job queue + state + logs |
~/.local/state/schedule-agent/session-ledger.jsonl |
append-only session ledger used for discovery and post-job reconciliation |
~/.local/share/schedule-agent/agent_prompts/ |
prompt files |
~/.config/schedule-agent/prompt-prefix-{claude,codex}.md |
per-agent prefix applied to every scheduled prompt |
$XDG_STATE_HOME, $XDG_DATA_HOME, and $XDG_CONFIG_HOME are honoured if set.
State, logs, and prompt dirs are chmod-ed to 0700 on creation — logs may contain secrets produced by the agent.
| Variable | Effect |
|---|---|
SCHEDULE_AGENT_EDITOR |
Editor for prompt/prefix editing (wins over $EDITOR) |
SCHEDULE_AGENT_STALE_MINUTES |
Minutes a running job must be idle (no log writes) before the recovery path force-marks it failed. Default 60, minimum 1. |
SCHEDULE_AGENT_POST_HOOK |
Optional shell command fired after every job finishes. Receives JOB_ID, JOB_TITLE, JOB_RESULT (success/failed), JOB_EXIT_CODE, JOB_LOG_FILE in its environment. Failures are swallowed. |
SCHEDULE_AGENT_MIN_CLAUDE / SCHEDULE_AGENT_MIN_CODEX |
Override the preflight minimum known-good version per agent. |
python3 -m pip install -e ".[dev]"
make checkmake check runs the same gate CI enforces:
ruff check .ruff format --check .pytest -q
To block merges on failures, mark the quality workflow as a required status check in branch protection.
MIT
