Skip to main content

agent-proxy

A stdio wrapper around an interactive claude TUI that states turn boundaries instead of leaving clients to infer them. With --agent codex it wraps codex app-server instead, behind the same protocol. It replaces claudejson, an earlier unpublished wrapper, inheriting its pty and transcript machinery and replacing its turn accounting.

API.md is the wire contract — every command, every message, and what a client may assume about the order. Read it first if you are writing a client.

The design documents that argue why the protocol has this shape are not published with the code. What is here is meant to stand on its own: API.md for the contract, RELEASING.md for cutting a version, and the module docstrings for the reasoning — turn_tracker.py on what moves a turn, fallback.py on why a marker alone cannot be relied on to end one.

Status

v0.3.1 adds structured questions and client permission approvals, and is the first PyPI release as thinkeryzu-agent-proxy. The command remains agent-proxy and Python imports remain agentproxy. See the release notes for changes since v0.2.0. The earlier v0.3.0 release was withdrawn.

echo '{"type":"submit","text":"Reply with exactly: PONG"}' | agent-proxy
{"seq": 1, "type": "ready", "outstanding": {"user": 0, "unowned": 0, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}, "session_id": "…"}
{"seq": 2, "type": "ack", "turn": {"id": "t-1", "kind": "user"}, "outstanding": {"user": 1, "unowned": 0, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}}
{"seq": 3, "type": "turn_start", "turn": {"id": "t-1", "kind": "user"}, "outstanding": {"user": 1, "unowned": 0, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}}
{"seq": 4, "type": "text", "turn": {"id": "t-1", "kind": "user"}, "outstanding": {"user": 1, "unowned": 0, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}, "text": "PONG"}
{"seq": 5, "type": "turn_end", "turn": {"id": "t-1", "kind": "user"}, "outstanding": {"user": 0, "unowned": 0, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}, "basis": "marker:turn_duration", "outcome": "success", "usage": {"duration_ms": 1670, "output_tokens": 5}}

Unrecognised arguments go through to the inner claude, so agent-proxy --model opus works as expected. Set AGENTPROXY_LOG=/path/to/file to record the pty bytes, the stdin lines and the state transitions behind the stream.

The session flags are the exception — the proxy resolves those itself so it can name the session before it types anything. ready is always the first message and always carries a real session_id, including for --continue, a bare --resume, and --fork-session. Keep that id and agent-proxy --resume <id> picks the conversation up in a later process; see API.md for the full table.

agent-proxy --agent codex runs codex app-server as the inner agent instead, signed in with ChatGPT; the client reads the same stream, and ready says "agent": "codex". Its arguments are mapped onto codex's own or refused, and a few counts read differently, since codex keeps no list of background commands: see API.md.

Every requirement, constraint and success criterion that can be checked without a client has a test naming it, in tests/requirements/.

Both clients have migrated — r2d2box, which agent-desktop-env mounts, and a second client that is not public — so claudejson is retired.

v0.3.1 adds structured agent questions for both backends. Use agent-proxy --user-input (or add --agent codex) with a client that supports the new input_request / answer exchange. Questions expire after 24 hours by default; configure seconds with AGENTPROXY_INPUT_TIMEOUT_S. Claude input support requires Claude Code >= 2.1.316. See API.md: User input for the full contract and Codex setup. Existing invocations keep questions disabled.

Use agent-proxy --no-bypass-permissions (also with --agent codex) to use native permission checks and ask through the client's choice-prompt flow. The flag implies --user-input. Permission prompts show the operation and Allow once / Deny choices, with the same configurable 24-hour deadline. Dismissal and expiry deny. See permission approvals for backend policies and supported approval types. Permission choices are available in v0.3.1.

Installing

The Claude backend requires Claude Code ≥ 2.1.221 for its explicit --autocompact 1m launch default; caller overrides are described in API.md.

The PyPI distribution is thinkeryzu-agent-proxy; the command is agent-proxy. uv uses PyPI and needs no separate publication step.

uv tool install thinkeryzu-agent-proxy
agent-proxy --help

Run without a permanent tool installation:

uvx --from thinkeryzu-agent-proxy agent-proxy --help

For a project environment, use uv pip install thinkeryzu-agent-proxy or pip install thinkeryzu-agent-proxy. Pin with thinkeryzu-agent-proxy==0.3.1 when needed. Claude/Codex must be installed and authenticated separately.

Older GitHub tags used distribution metadata named agent-proxy; update requirements to thinkeryzu-agent-proxy for current releases. Python imports and the command name are unchanged. GitHub also provides a wheel, source archive and installable tag:

pip install 'git+https://github.com/ThinkerYzu/agent-proxy@v0.3.1'

RELEASING.md covers Trusted Publisher setup, validation, publication and withdrawal.

Commands

What you want Command
a prompt answered {"type": "submit", "text": "…"} on stdin
answer an agent question (with --user-input) {"type":"answer","request":"i-1","answers":{"q-1":{"choices":["o-2"]}}} on stdin
authoritative state, any time {"type": "status"} on stdin
a turn taken back — dropped if still queued, interrupted with Esc if running {"type": "cancel", "turn": "t-6"} on stdin; the turn ends outcome: "cancelled" once the inner is seen to stop
to know which ack is yours add a "ref" of your own to a command — it comes back on the answer
the tracker against a recorded session, no claude python3 -m agentproxy.replay tests/fixtures/synthetic-session.jsonl --assert-drained

What it emits

Line-delimited JSON. Every message carries seq, its type, every outstanding count, and — when it belongs to a turn — that turn's id and kind:

{"seq": 41, "type": "turn_start", "turn": {"id": "t-6", "kind": "unowned"},
 "outstanding": {"user": 0, "unowned": 1, "background": 0, "monitor": 0, "agent": 0, "orphan": 0}, "caused_by": "bsynthimport"}

That one line is the project in miniature: a turn nobody prompted, named, and tied to the background command whose completion woke it.

Layout

Path What it is
API.md the stdio protocol reference for clients
RELEASING.md how a release is cut, and what a version number promises
.github/workflows/ CI on every push, a GitHub Release on every v* tag
src/agentproxy/turn_tracker.py the turn table, the task table, binding and end-of-turn; fed by transcript entries, or by stated facts for an agent that reports its own boundaries
src/agentproxy/protocol.py the envelope and the stdout writer
src/agentproxy/commands.py the stdin command parser, the same for every inner agent
src/agentproxy/codex_cli.py the process boundary for --agent codex: startup, the app-server's pipes, the loop
src/agentproxy/codex_proxy.py the codex wiring: send gate, cancel, the two fallbacks, refused server requests
src/agentproxy/codex_session.py codex notifications as turns and tasks, no I/O
src/agentproxy/app_server.py JSON-RPC with the app-server: ids, responses, server requests
src/agentproxy/inner_codex.py codex arguments mapped or refused, the environment, which thread to open
src/agentproxy/proxy.py the wiring — every decision, no process IO
src/agentproxy/cli.py the process boundary — pty, transcript, stdout, signals
src/agentproxy/liveness.py idle or active, settled, and the two dialogs that block input
src/agentproxy/fallback.py ending a turn that no marker closed
src/agentproxy/submit_queue.py accepted prompts, typed one at a time
src/agentproxy/inner_claude.py argv, env and session id for the inner spawn
src/agentproxy/orphans.py processes the inner lost track of: the subreaper flag, adoption, reaping, the orphan count
src/agentproxy/replay.py replay a recorded transcript, with no live claude
src/agentproxy/{pty_driver,io_loop,screen_model,input_encoder,transcript_tailer,ids,log}.py inherited from claudejson
tools/make_synthetic_fixture.py build the test session out of real entry shapes
tools/capture_fixture.py cut a fixture out of a real session transcript
tests/harness.py a proxied session with the pty taken out, shared by the suites below
tests/fake_app_server.py, tests/codex_harness.py a stand-in codex app-server built from recorded message shapes, and a codex session driven against it
tests/requirements/ one test group per SPEC requirement, constraint and success criterion
tests/requirements/reference_client.py a client with no inference layer, used to check the protocol keeps its word

Building and testing

python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest -q

396 tests, none of which needs a claude or codex binary (one of them proves the subreaper mechanism on the real kernel, in a subprocess; the codex ones run against tests/fake_app_server.py). Ten more do — seven against claude, three against codex — and are opt-in twice over: a marker alone deselects nothing on a bare pytest, and these spend real quota:

AGENTPROXY_RUN_LIVE=1 pytest -q -m live                               # both
AGENTPROXY_RUN_LIVE=1 pytest -q -m live tests/live/test_live_session.py  # claude
AGENTPROXY_RUN_LIVE=1 pytest -q -m live tests/live/test_live_codex.py    # codex

The codex tests are skipped when there is no codex on the path, and need it signed in with ChatGPT. Worth running when either binary changes version: they are the only tests that read what the installed binary actually writes — claude's transcript entries, codex app-server's messages, whose protocol codex marks experimental — rather than the shapes the fixtures model.

Fixtures

tests/fixtures/synthetic-session.jsonl is a 93-entry session assembled by tools/make_synthetic_fixture.py. Its conversation is invented; every entry shape in it — envelope fields, content blocks, marker subtypes, the two background-launch wordings, the queue-operation pairs, the cluster /compact writes after its boundary — was copied from a real recorded session. The tracker reads only the shapes, so nothing is lost, and the repository carries no transcript of real work.

Its sidecar, synthetic-session.prompts.json, names which entries were prompts a client typed — provenance a recording cannot carry on its own.

Regenerate with:

python3 tools/make_synthetic_fixture.py

The output is byte-stable, so a regeneration shows up as a reviewable diff and a test fails if the committed fixture drifts from the builder.

To check the tracker against a real session on your own machine — worth doing when claude changes shape, and not something to commit:

python3 tools/capture_fixture.py ~/.claude/projects/<dir>/<session>.jsonl /tmp/<name>.jsonl
python3 -m agentproxy.replay /tmp/<name>.jsonl --quiet --assert-drained

Metadata

Release files for thinkeryzu-agent-proxy 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for thinkeryzu-agent-proxy 0.3.1
File Size Uploaded Provenance Actions
thinkeryzu_agent_proxy-0.3.1.tar.gz 118.8 kB Without provenance Details for thinkeryzu_agent_proxy-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for thinkeryzu-agent-proxy 0.3.1
File Interpreter ABI Platform Actions
thinkeryzu_agent_proxy-0.3.1-py3-none-any.whl Python 3 none any Details for thinkeryzu_agent_proxy-0.3.1-py3-none-any.whl

Total release size: 224.8 kB

Release files / thinkeryzu_agent_proxy-0.3.1.tar.gz

Download URL thinkeryzu_agent_proxy-0.3.1.tar.gz
Size 118.8 kB
Tags Source
SHA-256 checksum
How to use checksums
1885bdfe1b1551b7252fe51b8da9cef78c4ddb41cbd107487fe23884fd4d0a17
BLAKE2b-256 checksum
How to use checksums
d89c7a2f6a9c5e664b20abb0cabc661fbe21883ffbb341f3c2279699ec9716f1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / thinkeryzu_agent_proxy-0.3.1-py3-none-any.whl

Download URL thinkeryzu_agent_proxy-0.3.1-py3-none-any.whl
Size 106.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7f38355cd3c2652568ab79a957a1653840f4777eacc2df0cd577549621bb4462
BLAKE2b-256 checksum
How to use checksums
7eb968b538a1f6f0cef897743012b67f50bfac8ce19a223674eb51e830a97e98
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.15 {"installer":{"name":"uv","version":"0.12.15","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for thinkeryzu-agent-proxy 0.3.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.3.1 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page