@@ -26,9 +26,13 @@ the agent).
2626┌───────────────▼──────────────────────────────────────┴───────────────┐
2727│ FastAPI app (TRUSTED) │
2828│ │
29- │ routes/ auth · conversations(+files, runs, artifacts) │
29+ │ routes/ HTTP only: dependencies, status codes, schemas │
30+ │ auth · conversations(+files, runs, artifacts) │
3031│ billing · secrets │
31- │ controllers/ manager.py run lifecycle, event fan-out (subscribe) │
32+ │ controllers/ all business logic │
33+ │ domain accounts · conversations · files · runs │
34+ │ secrets · usage (rules, persistence, refusals) │
35+ │ machinery manager.py run lifecycle, event fan-out (subscribe) │
3236│ protocol.py JSONL line parsing │
3337│ runner.py ServerRunner: one resident harness │
3438│ process per conversation │
7175 db.py SQLite engine/session (SQLAlchemy ORM)
7276 validation/ (request/response validation)
7377 schemas.py Pydantic request/response models
74- routes/ (view layer, HTTP)
78+ routes/ (view layer, HTTP only — no business logic )
7579 auth.py POST /auth/register /auth/login /auth/refresh /auth/me
7680 conversations.py CRUD + POST /{id}/files (upload) + GET/DELETE
7781 /{id}/artifacts (agent outputs) + POST /{id}/runs
7882 (start) + POST /{id}/answer + GET /{id}/stream (SSE)
7983 billing.py usage summary (token ledger)
8084 secrets.py CRUD (write-only read: value never returned)
81- controllers/ (business logic)
85+ controllers/ (all business logic)
86+ errors.py DomainError hierarchy; main.py renders it as
87+ {"detail": ...} so controllers never mention HTTP
88+ accounts.py register/login rules, token identity, admin check
89+ conversations.py ownership, CRUD, workspace teardown
90+ files.py upload sanitizing + size cap, uploads-vs-artifacts,
91+ traversal-safe artifact paths
92+ runs.py harness prompt augmentation, run access, cancel/answer
93+ secrets.py upsert + encryption-at-rest rules
94+ usage.py token ledger aggregation
8295 protocol.py Parse harness event lines (seq/run_id/result/usage)
8396 manager.py Controller: start_run, event pump, subscribe, cancel, answer
8497 runner.py Runner protocol + ServerRunner (resident serve process,
@@ -124,6 +137,16 @@ delivers the user's reply to a pending mid-run question (the agent's
124137Question tool / PlanExit confirm), and cancel is a protocol message —
125138no signal semantics.
126139
140+ ` notify ` lines carry the progress the UI renders, keyed by ` kind ` :
141+ ` tool_start ` (the round's tool names), ` tool_calls ` (the same round
142+ with each call's arguments, so a row reads ` Bash(command='ls -la') `
143+ rather than a bare ` Bash ` ), ` tool_running ` , ` tool ` , ` todos ` , ` compact ` ,
144+ ` retry ` , ` error ` , and ` ask ` for a mid-run question. ` tool_calls ` is
145+ additive — a harness that does not send it degrades to the names from
146+ ` tool_start ` . ` log ` lines are shown too, except session bookkeeping
147+ (the generated session title), which says nothing about what the agent
148+ is doing.
149+
127150## Configuration (env, prefix ` PAW_ ` )
128151
129152| var | default | note |
@@ -141,6 +164,25 @@ no signal semantics.
141164
142165## Design notes
143166
167+ - ** Routes are thin, controllers own the rules** : a route resolves
168+ dependencies, calls a controller, and maps the result to a response
169+ schema — nothing else. No route touches the DB, the filesystem or
170+ crypto. A controller refuses work by raising from
171+ ` controllers/errors.py ` (` NotFound ` , ` Conflict ` , ` InvalidRequest ` ,
172+ ` PayloadTooLarge ` , ` Unauthorized ` , ` Forbidden ` ); one handler in
173+ ` main.py ` renders that as FastAPI's own ` {"detail": ...} ` shape with
174+ the status the error type carries. So business logic never imports
175+ ` HTTPException ` , and a controller stays callable from a test, a CLI
176+ or a worker thread.
177+ - ** Stateless mechanism vs stateful policy** is the ` infra ` /
178+ ` controllers ` line, not "core vs supporting". ` infra ` holds
179+ primitives with no entities and no session — password hashing, JWT,
180+ Fernet ` encrypt ` /` decrypt ` — and imports nothing but ` infra ` .
181+ Anything that takes a ` Session ` , reads or writes an ORM entity, or
182+ can refuse a request lives in ` controllers ` , even for supporting
183+ areas like accounts and secrets. Moving those down would make the
184+ bottom layer import ` models ` and ` controllers.errors ` , inverting the
185+ dependency direction.
144186- ** Decoupling** : the harness is a black-box binary driven by its
145187 documented JSONL protocols (the same ` start ` /` delta ` /` notify ` /
146188 ` log ` /` result ` line shapes on the resident ` serve ` pipe and the
0 commit comments