AI Session Handler is a container-local, provider-agnostic task runner for short AI agent sessions. It runs each session-sized plan phase in a fresh agent process inside the AI workspace container and records durable state and transcripts.
The runner does not include provider adapters. It invokes an arbitrary command template supplied by the user, so Codex, Claude, container-local scripts, or any other agent CLI can be used through the same core process model.
All setup and execution commands for this project are intended to run in the container that owns the workspace, not on the user's workstation.
The provider-agnostic task-runner plan is implemented. The package includes
markdown phase parsing, durable state, worker prompt generation, subprocess
execution with transcripts, terminal marker handling, and run, status, and
init CLI commands.
Requires Python 3.12 or newer inside the container. From
/workspace/ai-session-handler:
python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]"If .venv/ already exists, rerun the editable install command after dependency
or packaging changes. Keep the virtualenv container-local and use it for this
repository's quality gates.
The repository-local entrypoint is exposed at:
.venv/bin/ai-session-handler --helpExamples below use the direct virtualenv path and assume they are run from
/workspace/ai-session-handler.
The core command shape is:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "your-agent-command-here"By default, the runner executes every remaining phase, using a fresh agent
process for each one, until the plan completes or a phase stops. Set
--max-phases 1 to stop after one successful phase, or use another positive
integer to cap the phases executed in one invocation.
--agent-cmd is a command template, not a shell script. The plan path determines
the plan workspace: run and status walk up from --plan to the nearest
.ai-session-handler, .git, or AGENTS.md marker. A full path to a plan in
another repository therefore uses that repository's config, state, prompts, and
transcripts.
Each phase declares its own execution workspace relative to the plan workspace.
The runner requires that directory to exist and contain an AGENTS.md at its
root, then starts that phase's fresh child process there. Use executables and
wrapper scripts visible from the execution workspace, or pass absolute container
paths for shared tools. Supported placeholders are:
{prompt_file}{workspace}(the selected phase's execution workspace){run_id}{transcript_file}{state_file}
Config is always read from .ai-session-handler/config.json in the inferred plan
workspace. Runner state is always stored as .ai-session-handler/<plan-stem>.json
in that same plan workspace. The config's max_phases value accepts a positive
integer or null; null is the default and runs the plan to completion.
Provider-specific setup belongs in wrapper scripts, not in runner internals.
The worker prompt is always written under .ai-session-handler/prompts/ and is
also piped to the agent process over stdin. Transcripts are written under
.ai-session-handler/transcripts/. The state path included in the worker prompt
is read-only context: workers must not modify it and must report their outcome
through exactly one terminal marker. The runner owns all durable state
transitions derived from that marker.
The runner streams child stdout and stderr to the same streams while also capturing both in the transcript. Terminal marker blocks are captured for parsing but hidden from the live console; the CLI prints the final phase result once after state is updated. Runner-owned errors, including invalid inputs and failed agent outcomes, are printed to stderr. Failed agent outcomes also print the transcript path and recent transcript output for debugging. Transcript headers distinguish the plan and execution workspace paths and include rendered argv; if a process exits without stdout or stderr, the transcript records that explicitly.
Pass --quiet to suppress live child stdout and stderr while still capturing
the complete transcript, parsing terminal markers, and printing the final phase
result. This is useful when invoking the handler from another agent session,
where streamed child output would otherwise consume the parent session's
context.
Create the optional example config and generated directories:
.venv/bin/ai-session-handler initRun all remaining phases:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "your-agent-command"Run only the next incomplete phase:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "your-agent-command" \
--max-phases 1Run against another repository by passing the full plan path:
.venv/bin/ai-session-handler run \
--plan /workspace/my-project/docs/plans/plan-22.mdRun without echoing agent progress while retaining the durable transcript:
.venv/bin/ai-session-handler run \
--plan /workspace/my-project/docs/plans/plan-22.md \
--quietPrint durable state and the latest transcript path:
.venv/bin/ai-session-handler status --plan docs/plans/plan-22.mdIf a phase stops, a later run refuses to continue by default and prints the stored stop message, latest transcript path, and recent transcript output when available. After human intervention, rerun that phase explicitly:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "your-agent-command" \
--retry-stoppedIf the plan file changes, the runner refuses to continue until the change is accepted and completed phase ids are verified to still exist:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "your-agent-command" \
--accept-plan-changeExecutable plans are Markdown files with explicit numbered phase headings:
## Phase 1: TitleAny Markdown heading level is accepted, but the heading text must be
Phase N: Title. Phase numbers must be positive, unique, and strictly
increasing. Phase bodies are preserved exactly between phase headings.
A plan follows Plan -> Phase -> Execution steps. Each phase represents one
fresh, session-sized context allocation and contains one or more concrete
execution steps. Ordinary phases should target roughly 25–35% of the context
window and about ten minutes of focused work, with 40% treated as a warning
threshold rather than a utilization target. Split broad work at independently
verifiable checkpoints even when consecutive phases edit the same files.
Every phase also requires exactly one ### Workspace section containing one
relative path such as . or ../transaction-service; see the canonical guide
for phase-boundary and workspace rules. A phase performs execution work only in
that repository. Work requiring another repository must use a separate phase,
even when the combined work would otherwise fit in one session.
Design documents are not executable plans. Headings such as Stage,
Workstream, and Issue, plus implementation-order lists, may describe useful
planning structure, but the runner only recognizes explicit phase headings.
See docs/plan-format.md for the canonical template and active format contract.
Codex can be invoked directly when its CLI reads work from stdin:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "codex exec"Claude or another CLI can be used the same way if it accepts stdin:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "claude"For provider-specific flags, shell setup, or file-based prompt ingestion, use a wrapper script and keep that behavior outside the runner:
.venv/bin/ai-session-handler run \
--plan docs/plans/plan-22.md \
--agent-cmd "./scripts/run-agent --prompt {prompt_file} --run {run_id}"For Codex high-reasoning runs, use the wrapper script from this repository's container-local virtualenv:
.venv/bin/ai-session-handler run \
--plan /workspace/my-project/docs/plans/plan-22.md \
--agent-cmd "/workspace/ai-session-handler/.venv/bin/ai-session-handler-codex-high --model gpt-5.5"That wrapper is shipped by this project but remains outside runner internals. It
sets Codex's high-reasoning mode, runs codex-lean exec with non-colored output,
streams stdout/stderr as Codex runs while filtering live terminal marker blocks,
captures the final message, and re-emits the single terminal marker from the
final message. This keeps the core runner provider-agnostic while preserving the
runner's exactly-one-marker contract. Omit --model to use the Codex CLI
default or the CODEX_MODEL value already present in the environment.
0: configured phase limit reached or all phases complete2: phase blocked3: phase needs clarification4: agent process failed, timeout, stop regex, missing marker, or multiple markers5: invalid plan, config, command template, or state
Invalid user inputs are printed to stderr with the file, command, marker, or state key to fix when that context is available.
.venv/bin/python -m ruff format .
.venv/bin/python -m ruff check . --fix
.venv/bin/python -m mypy src tests
.venv/bin/python -m pytest