This repository should converge on the same top-level conventions used across the reference repos:
apps/for thin runnable shellslibs/for reusable importable coderesources/for passive assetsdocs/for developer and user documentationtests/for clearly separated test layers
It should also catch up with the supported bridge inventory in
~/projects/instructor-php/packages/agent-ctrl, which currently targets these
CLI coding agents:
- Claude Code (
claude) - OpenAI Codex (
codex) - OpenCode (
opencode) - Pi (
pi) - Gemini CLI (
gemini)
Cursor is out of scope here because the target for this repo is CLI-based coding agents only.
- CLI agents only The direct integration layer talks to subprocess CLIs only. Do not build this repo around vendor SDKs or in-process Python agent libraries.
- Three explicit layers The repo follows the same command -> actions -> services split used across the user's other projects.
- Pydantic models are the schema All structured requests, responses, events, tool calls, usage objects, and capability objects should be Pydantic models.
- Unique Python package root
Use
py_agent_ctrlas the package root to avoid clashes with other Python libraries. Do not build the new architecture under a generic root such asagent_ctrl. uvonly Useuvfor dependency management, execution, linting, and tests.- Apps are thin
Any code under
apps/should parse args, call one adapter/action, and format output. No agent logic belongs there. - Resources stay passive
Skills, example prompts, sample event payloads, and migration assets live
under
resources/; parsing and orchestration live underlibs/.
Even when a vendor also ships a Python SDK, the coding-agent behavior we care about is usually defined at the CLI boundary:
- session continuation and resume semantics
- sandbox and approval controls
- tool invocation behavior
- stream-json / JSONL event formats
- working-directory and additional-directory handling
Keeping the direct bridge strictly CLI-based gives this repo:
- parity with the PHP
agent-ctrlpackage - one execution model across all supported agents
- simpler docs and migration guidance
- fewer feature gaps between providers
If a CLI happens to be implemented in Python, that does not change the design: we still treat it as an external CLI bridge.
The command layer contains thin delivery surfaces only.
Responsibilities:
- parse CLI or Python-facing inputs
- choose one action
- invoke the action layer
- format output for the selected delivery surface
Non-responsibilities:
- subprocess execution
- provider-specific parsing
- bridge-specific normalization logic
Examples:
apps/cli/- the public Python facade under
libs/py_agent_ctrl/api/
The actions layer owns invokeable use cases and the normalized API.
Responsibilities:
- accept normalized request models
- orchestrate bridge and core services
- normalize provider-specific outputs into common response and event models
- expose use cases such as execute, stream, resume, continue, and capabilities
- keep provider-specific behavior isolated behind explicit action inputs
Non-responsibilities:
- raw subprocess management
- provider-native JSONL parsing
- CLI argv construction
Typical files:
api/models.pyapi/events.pyapi/contracts.pyapi/facade.pyactions/execute.pyactions/stream.pyactions/sessions.pyactions/agents.py
The services layer owns implementation details, including the direct bridges.
Responsibilities:
- binary discovery
- argv and environment construction
- subprocess execution
- stdout/stderr and JSONL parsing
- provider-native request/event/capability models
- provider-specific bridge behavior
Non-responsibilities:
- public API shaping for callers
- command-line presentation
- pretending all provider features are portable
Typical files:
services/core/subprocess.pyservices/core/binaries.pyservices/core/env.pyservices/bridges/<provider>/models.pyservices/bridges/<provider>/bridge.pyservices/bridges/<provider>/command_builder.pyservices/bridges/<provider>/parser.py
Each agent integration still has two concerns, but they live inside the three-layer architecture:
- direct bridge Provider-specific CLI integration in the services layer
- normalized adapter behavior Action-layer mapping into the common API
The implementation contract is therefore:
command -> actions -> services
py-agent-ctrl/
├── apps/
│ └── cli/ # Thin smoke-test / demo / ops CLI
│
├── docs/
│ ├── dev/
│ │ └── architecture.md
│ └── user/ # End-user docs, migration guides, examples
│
├── libs/
│ └── py_agent_ctrl/
│ ├── __init__.py
│ ├── api/
│ │ ├── models.py # Pydantic common request/response models
│ │ ├── events.py # Pydantic normalized event models
│ │ ├── ids.py # Execution/session/tool-call IDs
│ │ ├── contracts.py # Bridge / adapter protocols
│ │ ├── capabilities.py # Common capability model
│ │ └── facade.py # AgentCtrl facade
│ │
│ ├── actions/
│ │ ├── execute.py
│ │ ├── stream.py
│ │ ├── sessions.py
│ │ └── agents.py
│ │
│ └── services/
│ ├── core/
│ │ ├── config.py
│ │ ├── errors.py
│ │ ├── binaries.py
│ │ ├── env.py
│ │ └── subprocess.py
│ └── bridges/
│ ├── claude_code/
│ │ ├── models.py
│ │ ├── bridge.py
│ │ ├── command_builder.py
│ │ └── parser.py
│ ├── codex/
│ ├── opencode/
│ ├── pi/
│ └── gemini/
│
├── resources/
│ └── skills/
│ └── upgrade/
│ └── SKILL.md
│
├── tests/
│ ├── unit/
│ ├── integration/
│ ├── feature/
│ └── regression/
│
├── pyproject.toml
└── README.md
The Python package should keep a simple top-level facade, aligned with the PHP package but expressed in Python naming:
from py_agent_ctrl import AgentCtrl
response = AgentCtrl.claude_code().execute("Summarize this repository.")
response = AgentCtrl.codex().execute("Write a refactoring plan.")
response = AgentCtrl.gemini().execute("Review the tests.")The normalized API should use Pydantic models for:
AgentResponseAgentRequestAgentTextEventAgentToolCallTokenUsageBridgeCapabilities
Provider-specific adapters may expose extra fluent methods, but they should all terminate in the same normalized response shape.
This refactor is a clean cut to the new layout.
The current flat package under ./agent_ctrl is legacy structure, not a target
surface to preserve. The implementation should move into libs/py_agent_ctrl/,
and the old root-level layout should be removed once the new structure is in
place.
That means:
- no compatibility shim rooted in
./agent_ctrl - no requirement to preserve
ClaudeCodeClient - no requirement to keep dataclass-based event or response models
- downstream migration is handled through updated docs and upgrade guidance, not parallel in-repo architectures
- the canonical Python import root after migration is
py_agent_ctrl
The target public API is simply:
from py_agent_ctrl import AgentCtrlCurrent repo functionality already covers a subset of this bridge:
- prompt execution
- streaming text/tool events
- session resume and continue
- permission mode
- additional directories
This should be the first bridge extracted into the new layout.
Catch up with PHP support for:
- sandbox modes
- approval / bypass modes
- image input
- session/thread continuity
- normalized file change, bash, MCP, web-search, and reasoning items
Catch up with PHP support for:
- provider-prefixed models
- named agents
- file attachments
- session sharing and titles
- usage and cost reporting
Catch up with PHP support for:
- provider and model selection
- thinking levels
- tool allowlists
- skill loading
- extension loading
- session directory controls
- usage and cost reporting
Catch up with PHP support for:
- approval modes
- sandbox flag
- extensions
- allowed MCP servers
- policy files
- allowed tools
- session continuation
- usage reporting
- add
docs/dev,docs/user - add
tests/unit,tests/integration,tests/feature,tests/regression - create
libs/py_agent_ctrl/ - wire packaging directly to the new layout
- plan removal of the legacy root
agent_ctrl/tree as part of the migration
- define normalized request, response, event, usage, and tool-call models
- define bridge and adapter contracts
- add execution ID distinct from provider session ID
- define common error types and parse diagnostics
- move Claude-specific command building and parsing into
services/bridges/claude_code/ - expose Claude through normalized actions and the common facade
- remove the old flat Claude implementation once parity is reached
- migrate existing tests to the new
tests/unitlayout
- implement
AgentCtrl.claude_code() - implement
AgentCtrl.codex() - implement
AgentCtrl.opencode() - implement
AgentCtrl.pi() - implement
AgentCtrl.gemini()
Builders can be added incrementally, but the facade shape should be fixed early.
Port in the order that minimizes migration risk:
- Claude Code parity
- Codex
- Gemini
- OpenCode
- Pi
The order above favors the highest-likelihood Python demand while still keeping the architecture ready for all five agents from the start.
tests/unit/Pydantic models, command builders, parsers, adapterstests/integration/subprocess execution against real or stubbed CLIstests/feature/end-to-end bridge flows from facade to normalized responsetests/regression/frozen payloads for stream parsing and migration regressions
Use uv only. Examples:
uv run pytest tests/unituv run pytest tests/integrationuv run ruff check .
Do not recommend or document direct python, python3, pip, or pip3
commands in this repo.
When downstream code only needs a coding agent result, it should prefer the common adapter API. When it needs provider-only controls, it should still use the provider adapter, but receive the same normalized response models.
That gives this repo the right split:
- direct bridge = stable place for raw CLI behavior
- common adapter = stable place for client-facing portability
This is the architecture to implement.