88
99The trusted side of an agent platform: API server, controller, auth,
1010billing, secrets. The untrusted side — the agent runtime itself — is
11- ` python-agent-harness ` , executed as a ** subprocess** with
12- ` python-agent-harness headless --json ` and consumed as a JSON-lines
13- event stream. The harness is never imported; the repos stay decoupled
14- (the harness only needs to be on PATH of whatever runs the agent).
11+ ` python-agent-harness ` , executed as a ** resident subprocess** per
12+ sandbox: ` python-agent-harness serve ` , a bidirectional JSON-lines
13+ protocol over stdin/stdout. The harness is never imported; the repos
14+ stay decoupled (the harness only needs to be on PATH of whatever runs
15+ the agent).
1516
1617```
1718Coding Space Server
1819 │
1920 ┌──────▼─────────┐
2021 │ Agent Controller│ app.controller
2122 └──────┬─────────┘
22- │ subprocess: python-agent-harness headless --json
23+ │ resident subprocess: python-agent-harness serve (JSONL over pipes)
2324 ┌──────▼─────────┐
24- │ Sandbox │ app.controller.runner (LocalRunner now, Docker later)
25+ │ Sandbox │ app.controller.runner (ServerRunner now, Docker later)
2526 │ agent process │
2627 │ tools, bash, … │
2728 └─────────────────┘
@@ -33,7 +34,7 @@ Controller agent-generated commands
3334Auth (JWT) repository, builds
3435Billing (usage ledger)
3536Secrets (Fernet)
36- Runner (sandbox manager contract; LocalRunner today, Docker later)
37+ Runner (sandbox manager contract; ServerRunner today, Docker later)
3738```
3839
3940## Layout
4950 schemas.py Pydantic request/response models
5051 routes/
5152 auth.py POST /auth/register /auth/login /auth/refresh /auth/me
52- conversations.py CRUD + POST /{id}/runs (start) + GET /{id}/stream (SSE)
53+ conversations.py CRUD + POST /{id}/runs (start) + POST /{id}/answer
54+ + GET /{id}/stream (SSE)
5355 billing.py usage summary (token ledger)
5456 secrets.py CRUD (write-only read: value never returned)
5557 controller/
56- protocol.py Parse harness --json lines (seq/run_id/result/usage)
57- manager.py Controller: start_run, event pump, subscribe, cancel
58- runner.py Runner protocol + LocalRunner (subprocess exec, cancel )
58+ protocol.py Parse harness event lines (seq/run_id/result/usage)
59+ manager.py Controller: start_run, event pump, subscribe, cancel, answer
60+ runner.py Runner protocol + ServerRunner (resident serve process )
5961 static/index.html Minimal chat UI (EventSource -> runs, fetch -> API)
6062```
6163
@@ -71,6 +73,26 @@ uvicorn app.main:app --reload # http://127.0.0.1:8000 (UI at /)
7173` PAW_HARNESS__CMD ` at the absolute binary if it is not. Auth: create
7274a user via ` /auth/register ` , then log in; the UI does this for you.
7375
76+ ## The serve protocol (harness side)
77+
78+ One sandbox = one long-lived ` serve ` process. The web side writes
79+ ops, the harness answers with events:
80+
81+ ```
82+ host → harness: {"op": "submit", "prompt": ..., "run_id": ...}
83+ {"op": "answer", "run_id": ..., "answers": [...]}
84+ {"op": "cancel", "run_id": ...} / {"op": "ping"} / {"op": "shutdown"}
85+ harness → host: {"type": "ready"} then per-run
86+ start/delta/notify/log lines and a terminal
87+ {"type": "result", "answer": ..., "usage": ..., "cancelled": ...}
88+ ```
89+
90+ Because the process is resident: conversation history persists across
91+ turns (multi-turn memory), no per-turn interpreter spawn, ` answer `
92+ delivers the user's reply to a pending mid-run question (the agent's
93+ Question tool / PlanExit confirm), and cancel is a protocol message —
94+ no signal semantics.
95+
7496## Configuration (env, prefix ` PAW_ ` )
7597
7698| var | default | note |
@@ -80,23 +102,24 @@ a user via `/auth/register`, then log in; the UI does this for you.
80102| ` PAW_ACCESS_TOKEN_MINUTES ` | ` 30 ` | JWT access TTL |
81103| ` PAW_REFRESH_TOKEN_DAYS ` | ` 14 ` | JWT refresh TTL |
82104| ` PAW_HARNESS__CMD ` | ` python-agent-harness ` | harness binary |
83- | ` PAW_HARNESS__CWD ` | ` "" ` | agent workspace dir per run |
84- | ` PAW_HARNESS__MAX_ROUNDS ` | unset | round budget forwarded to ` --max-rounds ` |
85- | ` PAW_HARNESS__TIMEOUT ` | unset | wall-clock budget forwarded to ` --timeout ` |
86- | ` PAW_RUNNER ` | ` local ` | ` local ` only; docker later |
105+ | ` PAW_HARNESS__CWD ` | ` "" ` | agent workspace dir per sandbox |
106+ | ` PAW_HARNESS__TIMEOUT ` | unset | host-side wall-clock budget for one run |
107+ | ` PAW_RUNNER ` | ` server ` | ` server ` only; docker later |
87108| ` PAW_SANDBOX__TTL_SECONDS ` | ` 300 ` | idle reaper TTL |
88109
89110## Design notes
90111
91112- ** Decoupling** : the harness is a black-box binary driven by its
92- documented ` headless --json ` protocol (` start ` /` delta ` /` notify ` /
93- ` log ` /` result ` lines, ` seq ` for ordering, ` run_id ` for correlation,
94- ` usage ` for billing). No imports, no shared state; the web side
95- can be versioned and deployed independently.
96- - ** Runs are processes** : one conversation turn = one harness exec =
97- one ` Run ` row; events stream to subscribers over SSE exactly as the
98- harness emitted them (plus run lifecycle events), and the ` result `
99- line lands in the DB.
113+ documented JSONL protocols (the same ` start ` /` delta ` /` notify ` /
114+ ` log ` /` result ` line shapes on the resident ` serve ` pipe and the
115+ one-shot ` headless --json ` pipe; ` seq ` for ordering, ` run_id ` for
116+ correlation, ` usage ` for billing). No imports, no shared state; the
117+ web side can be versioned and deployed independently.
118+ - ** Runs are protocol turns, not process lifecycles** : one
119+ conversation turn = one ` op:submit ` = one ` Run ` row; the resident
120+ process survives the run and serves the next turn. Events stream to
121+ subscribers over SSE exactly as the harness emitted them (plus run
122+ lifecycle events), and the ` result ` line lands in the DB.
100123- ** Secrets** are Fernet-encrypted at rest and never returned by the
101124 API; they are meant to be injected into the sandbox environment by
102125 the sandbox manager (not exposed to agents via the API).
0 commit comments