agent-sequencer is an MCP server + Claude Code skill that lets an AI agent step-execute
a classical program (a "sequencer program" written as a Python generator) the way a debugger
drives a process. Decision logic is owned by the program; the AI agent acts as a thin driver
that executes each yielded Instruction and submits the result back. Guardrails live in code,
not in prompts, so long-running tasks remain stable as the conversation context degrades.
It is developed and maintained by OPENSPHERE Inc. under the MIT license.
- Repository language: Python ≥ 3.11
- Dependency / environment manager: uv (Astral)
- Distribution: Claude Code plugin (git-based; PyPI publication deferred to Phase 2)
- Transport: MCP stdio (one server per Claude Code session)
- Current version: 0.1.0
agent-sequencer/
├── README.md # English top-level README (entry point)
├── README_ja.md # Japanese translation of the top README
├── LICENSE # MIT
├── pyproject.toml # Python package definition (Hatchling build backend)
├── uv.lock # uv lockfile
├── .gitignore / .gitattributes # LF/CRLF unification
├── .mcp.json # Plugin-bundled MCP server registration
│
├── src/
│ └── agent_sequencer/ # Python package
│ ├── __init__.py # __version__
│ ├── __main__.py # CLI entry point (agent-sequencer command)
│ ├── api.py # Yield types: Instruction / Done / Abort / Progress / Context / StepFailed
│ ├── runtime.py # Driver: generator runtime + jsonschema validation + on_invalid retry
│ ├── instance.py # Instance + InstanceStore (per-instance asyncio.Lock)
│ ├── registry.py # ProgramRegistry: program discovery, compile()+exec() loader, hot-reload rescan
│ ├── persistence.py # JSONL EventLog, deterministic replay, disk-TTL prune
│ └── tools.py # 7 MCP tools (sequencer_list_programs / start / current / next / resume / close / list)
│
├── tests/ # pytest test suite
│ ├── conftest.py # Shared fixtures (bundled_programs_dir, state_dir)
│ ├── test_runtime.py # Driver behaviour
│ ├── test_registry.py # Program loading, first-match wins, hot rescan diff
│ ├── test_persistence.py # EventLog append/read, prune by TTL
│ └── test_review_rounds.py # End-to-end Driver run of the bundled review-rounds program
│
├── .claude-plugin/
│ ├── plugin.json # Claude Code plugin manifest
│ └── marketplace.json # Marketplace listing (the repo registers itself)
│
├── skills/
│ └── agent-sequencer/ # The Claude Code skill (auto-loaded by the plugin)
│ ├── SKILL.md # Driving rules (kept short; loaded into agent context)
│ ├── README.md # Skill-level README
│ ├── docs/
│ │ └── authoring-programs.md # Sequencer-program author's guide
│ └── programs/ # Plugin-bundled sequencer programs (passed via AGENT_SEQUENCER_PROGRAMS_DIR)
│ ├── README.md
│ ├── review_rounds.py # The bundled review-rounds program
│ └── review_rounds/ # Self-contained bundle (skills, agents, scripts)
│ ├── README.md
│ ├── agents/ # python-sensei / sequencer-sensei / prompt-sensei
│ ├── scripts/ # fetch-diff.sh / rm-tmp.sh / render-review.py
│ └── skills/ # sequencer-review / sequencer-review-respond / sequencer-review-resolve
│
└── .github/
└── workflows/
└── ci.yml # pytest matrix (3 OS × 3 Python) + uv-tool-install smoke test
- uv ≥ 0.5 — Required. Install via the official script (
curl -LsSf https://astral.sh/uv/install.sh | shon Unix; the PowerShell installer on Windows). Avoid pip-installed uv. - Python 3.11–3.13 — uv will fetch a managed Python interpreter automatically if none is on PATH.
No pre-pip install step is needed — uv resolves and installs into a project-local .venv/.
git clone https://github.com/OPENSPHERE-Inc/agent-sequencer.git
cd agent-sequencer
uv sync # install runtime + dev dependencies
uv run agent-sequencer --helpuv run --project . agent-sequencer --watch--watch enables hot reload of programs/*.py (2-second throttle). The server speaks
JSON-RPC over stdio — when launched from a terminal it just sits waiting for client input.
Real use is via Claude Code; see .mcp.json and the README's "Development setup" section.
uv run pytest # all tests
uv run pytest tests/test_runtime.py -vuv tool install .
agent-sequencer --help # entry point installed via wheel
uv tool uninstall agent-sequencerCI runs this exact flow on every push (Linux / macOS / Windows × Python 3.11 / 3.12 / 3.13).
+---------+ MCP tool call +-------------------------+
| Claude | -----------------> | agent-sequencer |
| Code | | (MCP server, stdio) |
| (driver)| <----------------- | |
+---------+ next instruction | +-------------------+ |
| | | Sequencer program | |
| runs with own tools | | (Python generator)| |
| (Bash/Edit/Read/Skill) | +-------------------+ |
v | +-------------------+ |
user | | InstanceStore + | |
| | JSONL event log | |
| +-------------------+ |
+-------------------------+
The MCP server is a driver of a Python generator, not a workflow engine. A sequencer
program is just a function def run(ctx) that yields Instruction(...), receives the
agent's validated JSON result on the next iteration, and yields Done(...) /
Abort(...) to terminate.
- Yield types (
api.Instruction/Done/Abort/Progress/StepFailed) — What a sequencer program mayyield.Instruction.expect_schemais a JSON Schema that validates the agent's reply.on_invalid∈ {retry,abort}. - Context (
api.Context) — Passed torun(ctx). Exposesparams,env,publish_progress(...). - Driver (
runtime.Driver) — Owns one generator instance.start()advances to the first yield;send(result)validates the result againstexpect_schemaand advances (or re-issues with a freshstep_noon retry).throw(exc)injects aStepFailedinto the program. - InstanceStore (
instance.InstanceStore) — Process-wide registry ofInstanceobjects. Each instance carries its ownasyncio.Lockso concurrentsequencer_nextcalls on the sameinstance_idserialise. - ProgramRegistry (
registry.ProgramRegistry) — Discoversprograms/*.py, loads each viacompile() + exec()(deliberately bypassesimportlib's pyc cache so equal-size hot edits are picked up), recordssource_hashfor resume integrity.rescan()returns an added/updated/removed diff. - EventLog (
persistence.EventLog) — Append-only JSONL writer. Three event kinds:header,yield,result.read_events()+parse_header()+iter_results()support deterministic replay.prune_old_logs()deletes logs whose last yield is past their disk-TTL. - MCP tools (
tools.build_server()) — Registers the 7 MCP tools onto a FastMCP server.--watchwraps registry-touching tools with a throttledrescan().
A sequencer program must be deterministic. The replay-based sequencer_resume re-runs
the program from scratch and re-injects the recorded result events; if the program reads
the wall clock, calls random, performs I/O, or mutates module state it will diverge.
Operationally this means program authors must:
- Never call
time.time()/datetime.now()/random.*directly. Ask the agent via anInstructionif a value is required ({"timestamp": "..."}). - Never perform file/network/DB I/O inside
run(). - Use
ctx.params.get(key, default)rather thanctx.params.get(key) or default(theorform treats[],0, and""as missing). - Avoid module-level heavy initialization (
--watchre-execs the module on rescan).
The full author guidelines live in skills/agent-sequencer/docs/authoring-programs.md.
sequencer_start ─▶ RUNNING ─▶ AWAITING_RESULT ⇄ (sequencer_next) ─▶ TERMINAL_QUERYABLE
│
(Done / Abort / Exception) │
▼
(close / TTL / server stop)
│
▼
ARCHIVED
│
(disk TTL)
▼
PRUNED
- In-memory TTL after terminal — 10 min.
- Disk TTL (Done) — 30 days.
- Disk TTL (Abort / interrupted) — 7 days.
- Disk TTL (Failed / exception) — 90 days.
Prune runs once at server start. The age basis is the last yield's yielded_at
(falling back to mtime if the timestamp is unparseable).
All tools accept and return JSON; instance_id is mandatory on every tool that touches an
instance because MCP does not expose caller identity (see implementation-plan.md §4 for
the rationale).
sequencer_list_programs— Enumerate loaded programs. With--watch, triggers a throttled rescan first.sequencer_start— Create a new instance from a program + params. Returnsinstance_id,step_no,last_yield.sequencer_current— Re-fetch the most recent yield (used after compact / interrupt to re-sync). Read-only; never used as a decision input.sequencer_next— Validateresultagainst the pendingInstruction.expect_schema, thengen.send(result). Mandatoryfor_step_norejects out-of-sync submissions.sequencer_resume— Rebuild a Driver from JSONL via deterministic replay.source_hashmismatch returnsProgramChanged.sequencer_close— Idempotent release.delete_log=Truealso removes the JSONL.sequencer_list— Enumerate active or terminal instances (metadata only — never returnslast_yieldtext, to keep context small).
- Python style: PEP 8, type hints required on public APIs,
from __future__ import annotationsat the top of every module. - Docstrings: Google style (Args / Returns / Raises). Module docstrings open with a one-line summary.
- Comments / docstrings / prompts / READMEs are in English. The only Japanese file in the repo is
README_ja.md(a translation of the top README). - Strings in
Instruction.textand bundled skill.mdfiles are AI-facing prompts. Keep them imperative, structured, and free of decorative Markdown — Claude reads them, not humans. - No formatter is enforced yet. When ruff/black is added (Step 10 follow-up) the line length will be 100 cols.
- Imports: standard library → third-party → local, separated by blank lines.
The full guide is skills/agent-sequencer/docs/authoring-programs.md. The non-negotiable rules:
- Define
NAME(kebab-case),DESCRIPTION,PARAMS_SCHEMA, anddef run(ctx). - Every
Instructionmust specifyexpect_schemawith at leasttypeandrequired. - Always end with an explicit
yield Done(summary={...})oryield Abort(reason="..."). - Stay deterministic (see the constraint section above).
- If the program ships supporting files (sub-skills, agent definitions, scripts), bundle
them in an adjacent
<program-name>/directory and resolve paths viaPath(__file__).parent / "<program-name>"so the bundle is install-location independent (seeprograms/review_rounds.pyfor the canonical example).
- Lock discipline:
sequencer_nextmust run insideinstance.lock. After acquiring, re-checkis_terminal()andfor_step_nobecause the state may have advanced while the call was waiting. - Persistence ordering: write the
resultevent before callingDriver.send, then write the resultingyieldevent after — even ifsendraises. The JSONL must always reflect what was attempted, not just what succeeded. - No stdout output from the server. stdout is reserved for JSON-RPC. Use
logger(configured to stderr in__main__.py). - Avoid module-level state in
programs/*.py— the registry re-execs them on rescan. - Do not pickle generators. State is recovered through deterministic replay.
- Search-path resolution: tier 3 (auto-derived "skill-root/programs") was removed in
the public-repo restructure. Bundled programs are now passed via the
AGENT_SEQUENCER_PROGRAMS_DIRenv var (set by the plugin's.mcp.json) and are appended last in the search list, so a project-specific (<cwd>/.claude/sequencer/programs/) or user-wide (~/.claude/sequencer/programs/) program with the sameNAMEtransparently overrides the plugin-bundled copy. Don't reintroduce a__file__-based fallback in__main__.py— it can't span PyPI vs. plugin install layouts.
- Modules / functions / variables:
snake_case. - Classes:
PascalCase(e.g.,Driver,ProgramRegistry,EventLog). - Constants / module-level state:
UPPER_SNAKE_CASE(STATE_AWAITING_RESULT,KIND_DONE,DISK_TTL_COMPLETED). - Private helpers: leading underscore (
_resolve_search_paths,_last_yield_event). - MCP tool names:
sequencer_<verb>[_<noun>](sequencer_list_programs,sequencer_close). - Sequencer program file names:
snake_case.py. TheNAMEconstant inside iskebab-case.
- The MCP layer translates internal exceptions into typed error codes:
NotFound/AlreadyLoaded/CorruptedLog/ProgramChanged/ProgramNotFound/ReplayFailed/OutOfSync/BusyError/TerminalError/NotLoaded. - Use
logger.warningfor recoverable conditions (e.g., a JSONL line that fails to parse is skipped with a warning, not raised). - Never let an exception from a sequencer program propagate untouched — the Driver wraps
it into a terminal
erroryield withstate=failed.
pytestis the only supported test runner. Tests live intests/.pyproject.tomlconfigurestestpaths = ["tests"]andasyncio_mode = "auto".- Drive sequencer programs through
Driverdirectly for unit tests — no MCP server needed (seetests/test_review_rounds.pyfor the pattern). - State directory: tests use
tmp_pathvia thestate_dirfixture so they never touch the real~/.claude/sequencer/state/. - Encoding: always pass
encoding="utf-8"toPath.open/Path.read_text. On Windows the default codepage is cp932 and will corrupt the JSONL. - Windows file locking: a JSONL file held open by an
EventLogcannot beunlinked on Windows. Tests that delete logs mustclose()first.
When adding new functionality, also verify:
uv run agent-sequencer --helpstill works (entry point intact).- The
install-from-gitjob equivalent (uv tool install .) still produces a runnable CLI. - The bundled
review-roundsprogram still loads and its three convergence branches still hit (tests/test_review_rounds.py).
.github/workflows/ci.yml(trigger: push / PR tomaster/main) — (a)pytestmatrix on Linux × macOS × Windows × Python 3.11 / 3.12 / 3.13; (b)uv tool install .smoke test that validates the wheel is consumable byuvx --from git+....
There is currently no publish.yml (PyPI publication is a Phase 2 task).
Releases follow semver: 0.X.Y for pre-1.0, X.Y.Z post-1.0.
- Define the tool inside
tools.build_server()using FastMCP's@server.tool()decorator. Required params and types are derived from the function signature. - Decide the contract:
- Lock-required? (any tool that mutates instance state must run inside
instance.lock) - Watch-rescan-required? (any tool that consults
ProgramRegistry) - Permitted in which lifecycle states? (see
implementation-plan.md§4.3)
- Lock-required? (any tool that mutates instance state must run inside
- Add a unit test in
tests/test_tools.py(file does not exist yet — create it). - Update
skills/agent-sequencer/SKILL.mddriving rules andREADME.mdtool table. - Add the tool to the
permissions.allowlist in the README's "MCP tool permissions" example.
- Create
skills/agent-sequencer/programs/<name>.pydefiningNAME/DESCRIPTION/PARAMS_SCHEMA/run(ctx). - If it has supporting assets, place them in
skills/agent-sequencer/programs/<name>/(the registry only scans*.py, so subdirectories are safe). - Resolve any in-bundle paths via
Path(__file__).resolve().parent / "<name>"— never hardcode repo-relative paths. - Add a unit test under
tests/that drives the program throughDriverand asserts each branch. - Add the program to the
programs/README.mdtable and the SKILL.md "Bundled programs" section.
- The
_advance/send/throwmethods are the only places that touch the generator. Keep the order [validate → writeresultevent →gen.send→ writeyieldevent] intact, otherwise replay will diverge. - The
Done.summary/Abort.reasonextraction supports two paths: yieldedDone(...)andreturn {...}(which surfaces asStopIteration.value). Both must keep producing terminal yields with the rightkind.
- The JSONL format is part of the on-disk contract. Adding a new event kind requires a migration path for older logs (or a
min_log_versioncheck inparse_header). - TTL constants (
DISK_TTL_*) are public — bumping them changes user-visible disk retention.
- Use
compile() + exec(), notimportlib.util.spec_from_file_location/exec_module. The latter caches.pycfiles keyed on (mtime, size) and silently runs stale code when an edit produces a same-size, same-second rewrite — which broke--watchtesting in step 7. - The
source_hashissha256(read_bytes())— keep it that way so the value stays stable across platforms / line endings (the repo enforces LF via.gitattributes).
- Any code change that adds new user-facing prose in
README.mdrequires a matching update inREADME_ja.md. The two files are kept in sync manually for now. - All other docs / code comments / prompts stay English-only.
- Never write to stdout from inside the server — stdio is reserved for JSON-RPC frames. Logging is configured to stderr in
__main__.py; preserve that. ${CLAUDE_PLUGIN_ROOT}is only resolved by Claude Code when the server is launched from a plugin's.mcp.json. In a hand-written.mcp.json(the development-mode example in the README) it must be replaced with an absolute path.${workspaceFolder}is NOT a Claude Code substitution. Only VS Code expands that token; Claude Code passes it through verbatim. Always use absolute paths or${CLAUDE_PLUGIN_ROOT}.- The bundled
review_roundsskill.mdfiles reference each other and the bundle scripts viaskills/agent-sequencer/programs/review_rounds/...paths. That works when Claude Code's cwd is the agent-sequencer repo root. For plugin install (where cwd ≠ plugin root) the path becomes wrong; making it install-location independent is a tracked Step 10 follow-up. - Determinism is enforced by convention, not by sandbox. A program that calls
time.time()will silently diverge on replay. Code review is the only line of defence. AGENT_SEQUENCER_STATE_DIRmay contain unfinished sessions across crashes. The startupprune_old_logsonly removes entries past their disk TTL — fresh failures stay around for 90 days for post-mortem.- Hot reload (
--watch) does not affect running instances. A Driver captures itsrun_fnreference atstart(); subsequent rescans only update the registry for newsequencer_startcalls. .gitattributesforces LF for source files. Editing on Windows is fine, but do not configurecore.autocrlf=trueglobally — it bypasses the attribute and breakssource_hashreproducibility.- Python 3.11 is the floor.
from __future__ import annotationsand PEP 604 unions (X | Y) are used throughout.
- AI agents must respond in the language the user is using (Japanese ↔ English).
- Code, comments, prompts, and documentation written into the repo must remain in English.
Only
README_ja.mdis permitted to contain Japanese prose.
- The team leader focuses exclusively on orchestrating teammates and does not edit files itself.
- Team size: 3–5 members chosen by task.
- Each teammate works on different files to avoid edit conflicts.
- Begin with research / review, then dispatch the team for parallel execution.
- Do not use sub-agents for tasks that fit a team. Exception: parallel reviews and bulk translation may use sub-agents.
Custom agent definitions live under .claude/agents/ (when present in your local Claude Code setup). Recommended specialists for this repo:
- python-sensei — Python language correctness, PEP compliance, type hints, async/await, generator semantics.
- sequencer-sensei — agent-sequencer API itself:
Instruction/Done/Abort/Context, determinism, lifecycle,expect_schemadesign, bundling. - prompt-sensei —
Instruction.textdesign: structure, schema integrity, runaway-prevention constraints, template patterns. - devops-sensei —
pyproject.toml,uv.lock, GitHub Actions, plugin packaging.
The bundled review_rounds program already drives the first three of these in parallel as part of its review cycle.