|
2 | 2 |
|
3 | 3 | # python-agent-web |
4 | 4 |
|
5 | | -**web-based ai-agent platform.** |
| 5 | +**Web/API platform for python-agent-harness.** |
6 | 6 |
|
7 | 7 | </div> |
8 | 8 |
|
9 | | -A web-based ai-agent platform. |
| 9 | +The trusted side of an agent platform: API server, controller, auth, |
| 10 | +billing, 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). |
10 | 15 |
|
11 | | -. |
| 16 | +``` |
| 17 | +Coding Space Server |
| 18 | + │ |
| 19 | + ┌──────▼─────────┐ |
| 20 | + │ Agent Controller│ app.controller |
| 21 | + └──────┬─────────┘ |
| 22 | + │ subprocess: python-agent-harness headless --json |
| 23 | + ┌──────▼─────────┐ |
| 24 | + │ Sandbox │ app.controller.runner (LocalRunner now, Docker later) |
| 25 | + │ agent process │ |
| 26 | + │ tools, bash, … │ |
| 27 | + └─────────────────┘ |
| 28 | +
|
| 29 | +TRUSTED UNTRUSTED / ISOLATED |
| 30 | +───────────── ───────────────────── |
| 31 | +FastAPI app harness agent process |
| 32 | +Controller agent-generated commands |
| 33 | +Auth (JWT) repository, builds |
| 34 | +Billing (usage ledger) |
| 35 | +Secrets (Fernet) |
| 36 | +Runner (sandbox manager contract; LocalRunner today, Docker later) |
| 37 | +``` |
| 38 | + |
| 39 | +## Layout |
| 40 | + |
| 41 | +``` |
| 42 | +app/ |
| 43 | + main.py FastAPI app factory + routers + static UI |
| 44 | + core/ |
| 45 | + config.py Settings (pydantic-settings, env prefix PAW_) |
| 46 | + security.py JWT auth (access/refresh) + password hashing |
| 47 | + db.py SQLite engine/session (SQLAlchemy ORM) |
| 48 | + models.py User, Conversation, Run, UsageEvent, Secret |
| 49 | + schemas.py Pydantic request/response models |
| 50 | + routes/ |
| 51 | + auth.py POST /auth/register /auth/login /auth/refresh /auth/me |
| 52 | + conversations.py CRUD + POST /{id}/runs (start) + GET /{id}/stream (SSE) |
| 53 | + billing.py usage summary (token ledger) |
| 54 | + secrets.py CRUD (write-only read: value never returned) |
| 55 | + 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) |
| 59 | + static/index.html Minimal chat UI (EventSource -> runs, fetch -> API) |
| 60 | +``` |
| 61 | + |
| 62 | +## Quick start |
| 63 | + |
| 64 | +```bash |
| 65 | +python -m venv .venv && . .venv/bin/activate |
| 66 | +pip install -e ".[dev]" |
| 67 | +uvicorn app.main:app --reload # http://127.0.0.1:8000 (UI at /) |
| 68 | +``` |
| 69 | + |
| 70 | +`python-agent-harness` must be importable-on-PATH as a command; point |
| 71 | +`PAW_HARNESS__CMD` at the absolute binary if it is not. Auth: create |
| 72 | +a user via `/auth/register`, then log in; the UI does this for you. |
| 73 | + |
| 74 | +## Configuration (env, prefix `PAW_`) |
| 75 | + |
| 76 | +| var | default | note | |
| 77 | +|---|---|---| |
| 78 | +| `PAW_DB_URL` | `sqlite:///./paw.db` | any SQLAlchemy URL | |
| 79 | +| `PAW_SECRET_KEY` | dev default | **set in production** | |
| 80 | +| `PAW_ACCESS_TOKEN_MINUTES` | `30` | JWT access TTL | |
| 81 | +| `PAW_REFRESH_TOKEN_DAYS` | `14` | JWT refresh TTL | |
| 82 | +| `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 | |
| 87 | +| `PAW_SANDBOX__TTL_SECONDS` | `300` | idle reaper TTL | |
| 88 | + |
| 89 | +## Design notes |
| 90 | + |
| 91 | +- **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. |
| 100 | +- **Secrets** are Fernet-encrypted at rest and never returned by the |
| 101 | + API; they are meant to be injected into the sandbox environment by |
| 102 | + the sandbox manager (not exposed to agents via the API). |
| 103 | +- **Billing** is a token ledger: the controller snapshots |
| 104 | + `result.usage` (input/output/rounds) from the harness into |
| 105 | + `usage_events`, attributed to the user and conversation. |
0 commit comments