Skip to content

Commit a1d7ef7

Browse files
authored
Update README.md
1 parent 667c140 commit a1d7ef7

1 file changed

Lines changed: 46 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -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 │
@@ -71,14 +75,23 @@ app/
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
124137
Question tool / PlanExit confirm), and cancel is a protocol message —
125138
no 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

Comments
 (0)