Skip to content

Commit 4d19c60

Browse files
authored
Update README.md
1 parent d311eb9 commit 4d19c60

1 file changed

Lines changed: 34 additions & 43 deletions

File tree

‎README.md‎

Lines changed: 34 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ only needs to be on PATH of whatever runs the agent).
3939
│ protocol.py JSONL line parsing │
4040
│ runner.py ServerRunner: one resident harness │
4141
│ process per conversation │
42-
│ infra/ config (PAW_* env) · security (JWT, Fernet) │
43-
│ models/ SQLite (SQLAlchemy): users, conversations, runs, │
42+
│ infra/ config (PAW_* env) · db (engine + migrations) · │
43+
│ security (JWT, Fernet) │
44+
│ models/ ORM entities: users, conversations, runs, │
4445
│ files, usage ledger, secrets, sandboxes │
4546
└───────────────┬──────────────────────────────────────────────────────┘
4647
│ stdin/stdout pipes — bidirectional JSONL
@@ -68,47 +69,6 @@ fanned out to in-memory subscribers and re-emitted as an SSE `data:`
6869
frame, so the browser sees the same events the harness TUI renders (tool
6970
progress, todos, errors, mid-run questions).
7071

71-
## Layout
72-
73-
```
74-
app/
75-
main.py FastAPI app factory + routers + static UI
76-
models/ (model layer)
77-
__init__.py ORM models: User, Conversation, ConversationFile, Run,
78-
UsageEvent, Secret, Sandbox
79-
db.py SQLite engine/session (SQLAlchemy ORM)
80-
validation/ (request/response validation)
81-
schemas.py Pydantic request/response models
82-
routes/ (view layer, HTTP only — no business logic)
83-
auth.py POST /auth/register /auth/login /auth/refresh /auth/me
84-
conversations.py CRUD + POST /{id}/files (upload) + GET/DELETE
85-
/{id}/artifacts (agent outputs) + POST /{id}/runs
86-
(start) + POST /{id}/answer + GET /{id}/stream (SSE)
87-
billing.py usage summary (token ledger)
88-
secrets.py CRUD (write-only read: value never returned)
89-
controllers/ (all business logic)
90-
errors.py DomainError hierarchy; main.py renders it as
91-
{"detail": ...} so controllers never mention HTTP
92-
accounts.py register/login rules, token identity, admin check
93-
conversations.py ownership, CRUD, workspace teardown
94-
files.py upload sanitizing + size cap, uploads-vs-artifacts,
95-
traversal-safe artifact paths
96-
runs.py harness prompt augmentation, run access, cancel/answer
97-
secrets.py upsert + encryption-at-rest rules
98-
usage.py token ledger aggregation
99-
protocol.py Parse harness event lines (seq/run_id/result/usage)
100-
manager.py Controller: start_run, event pump, subscribe, cancel, answer
101-
runner.py Runner protocol + ServerRunner (resident serve process,
102-
per-conversation workspace cwd)
103-
views/ (static UI)
104-
index.html Chat UI: upload, task cards, SSE progress (tool status,
105-
todos, ask/answer), artifact downloads
106-
infra/ (cross-cutting infrastructure)
107-
config.py Settings (pydantic-settings, env prefix PAW_)
108-
security.py JWT auth (access/refresh) + password hashing
109-
secrets_store.py Fernet-encrypted secret values
110-
```
111-
11272
## Quick start
11373

11474
```bash
@@ -123,6 +83,26 @@ uvicorn app.main:app --reload # http://127.0.0.1:8000 (UI at /)
12383
Auth: create a user via `/auth/register`, then log in; the UI does this
12484
for you.
12585

86+
### Database & migrations
87+
88+
The schema is owned by Alembic (`migrations/`). Startup runs
89+
`upgrade_to_head()` automatically, so a fresh `uvicorn` run creates the
90+
schema and stamps `alembic_version` — no manual step for dev. Tests
91+
migrate each throwaway database the same way, so the whole suite runs
92+
against real migrations.
93+
94+
After changing a model, generate and review a migration:
95+
96+
```bash
97+
alembic revision --autogenerate -m "describe the change" # writes migrations/versions/*
98+
alembic upgrade head # apply it
99+
alembic check # models ⇆ migrations in sync
100+
```
101+
102+
`alembic check` reporting "No new upgrade operations detected" means the
103+
migrations match the models. SQLite uses batch mode for ALTERs; the
104+
migration URL comes from `PAW_DB_URL` (see `migrations/env.py`).
105+
126106
## The serve protocol (harness side)
127107

128108
One sandbox = one long-lived `serve` process.
@@ -212,3 +192,14 @@ session title), which says nothing about what the agent is doing.
212192
- **Billing** is a token ledger: the controller snapshots `result.usage`
213193
(input/output/rounds) from the harness into `usage_events`, attributed
214194
to the user and conversation.
195+
- **Observability**: every request gets an `X-Request-ID` (echoed if the
196+
client sent one), bound to a contextvar so every `paw.*` log line
197+
carries it; unhandled errors are logged with a traceback and returned
198+
as a 500 quoting the id. `PAW_LOG_JSON=true` switches to JSON lines.
199+
- **Rate limiting**: a process-local sliding-window limiter throttles the
200+
run endpoint per user (each run spends tokens) and login/register per
201+
IP, returning 429 + `Retry-After`. Per-instance only — a multi-instance
202+
deploy would need a shared store.
203+
- **Token revocation**: tokens carry a `ver` claim matched against the
204+
user's `token_version`; logout and password change bump it, so every
205+
outstanding token (all sessions) is rejected on next use.

0 commit comments

Comments
 (0)