You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(rpc): expose TransportKind and on_serve_start lifecycle hook
Workers (RPC implementations) had no way to learn which transport they
were bound to. CallContext.transport_metadata only carried HTTP fields
and was ambiguous for non-HTTP transports.
Add a coarse TransportKind enum (PIPE / HTTP / UNIX) plus a duck-typed
on_serve_start(self, kind) lifecycle hook fired once per process before
the first request. HTTP fires lazily via a Falcon middleware so
pre-fork servers (gunicorn, uwsgi) correctly run startup work in each
child. Capabilities (currently {"shm"}) ride alongside the kind so
zero-copy paths can be detected without bloating the enum. CallContext
gains a kind field for per-call branching.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,7 +107,9 @@ For HTTP transport, the wire protocol maps to separate endpoints: `POST /vgi/{me
107
107
108
108
**Stream state**: Streaming methods return a `Stream[S]` where `S` is a `StreamState` subclass. The state's `process(input, out, ctx)` method is called once per iteration. Producer streams (default `input_schema=_EMPTY_SCHEMA`) ignore the input and call `out.finish()` to end. Exchange streams set `input_schema` to a real schema and process client data.
109
109
110
-
**CallContext injection**: Server method implementations can accept an optional `ctx: CallContext` parameter. `CallContext` provides `auth` (`AuthContext`), `client_log()` (client-directed logging), `emit_client_log` (raw `ClientLog` callback), `transport_metadata` (e.g. `remote_addr` from HTTP), and a `logger` property returning a `LoggerAdapter` with request context pre-bound. The parameter is injected by the framework — it does **not** appear in the Protocol definition.
110
+
**CallContext injection**: Server method implementations can accept an optional `ctx: CallContext` parameter. `CallContext` provides `auth` (`AuthContext`), `client_log()` (client-directed logging), `emit_client_log` (raw `ClientLog` callback), `transport_metadata` (e.g. `remote_addr` from HTTP), `kind` (the active `TransportKind`), and a `logger` property returning a `LoggerAdapter` with request context pre-bound. The parameter is injected by the framework — it does **not** appear in the Protocol definition.
111
+
112
+
**Transport awareness**: Workers can read `RpcServer.transport_kind` (a `TransportKind` enum: `PIPE`, `HTTP`, `UNIX`) and `RpcServer.transport_capabilities` (currently `{"shm"}` when bound to a `ShmPipeTransport`). For one-shot startup work an implementation may define an `on_serve_start(self, kind: TransportKind) -> None` method (the `ServeStartHook` Protocol); the framework calls it once per process before the first dispatch. For pipe/unix the hook fires inside `RpcServer.serve(transport)`; for HTTP it fires lazily on the first request handled in the current process so pre-fork servers (gunicorn, uwsgi) correctly run it in each child. Hook exceptions propagate (and are logged via `logging.getLogger("vgi_rpc.rpc").exception` first); rebinding to a different kind re-fires the hook rather than raising. Per-call code can also branch on `ctx.kind`.
111
113
112
114
**Authentication**: `AuthContext` (frozen dataclass) carries `domain`, `authenticated`, `principal`, and `claims`. For HTTP transport, `make_wsgi_app(authenticate=...)` installs `_AuthMiddleware` that calls the callback on each request and populates `CallContext.auth`. Pipe transport gets anonymous auth by default. Methods can call `ctx.auth.require_authenticated()` to gate access.
Copy file name to clipboardExpand all lines: docs/api/auth.md
+14Lines changed: 14 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -65,6 +65,20 @@ Over pipe/subprocess transport, `ctx.auth` is always `AuthContext.anonymous()`.
65
65
66
66
`ctx.transport_metadata` provides transport-level information like `remote_addr` and `user_agent` (HTTP only). It's a read-only mapping populated by the transport layer.
67
67
68
+
`ctx.kind` exposes the active `TransportKind` (`PIPE`, `HTTP`, or `UNIX`) so methods can branch per-call:
Falls back to normal pipe IPC for batches that exceed the segment size.
90
90
91
+
## Transport awareness
92
+
93
+
Workers can ask which transport they are bound to — useful for tailoring startup work, enabling transport-specific metrics, or branching per-call behaviour. Three things are exposed:
94
+
95
+
-`RpcServer.transport_kind`: a `TransportKind` enum (`PIPE`, `HTTP`, `UNIX`) or `None` before serving begins.
96
+
-`RpcServer.transport_capabilities`: a `frozenset[str]` of capability flags. Currently `{"shm"}` when bound to a `ShmPipeTransport`; empty otherwise.
97
+
-`CallContext.kind`: per-call view of the same `TransportKind`, so methods that already accept `ctx` can branch without reaching for the server.
98
+
99
+
For one-shot startup work, an implementation may define an `on_serve_start(self, kind)` method. The framework calls it once per process before the first request is dispatched:
if ctx.kind is TransportKind.HTTPandself._cache isnotNone:
115
+
returnself._cache.get(key)
116
+
return load_from_disk(key)
117
+
```
118
+
119
+
The hook is duck-typed (no base class needed); a `ServeStartHook` Protocol is exported for users who want to type-hint their implementation. Hook exceptions propagate (and are logged via `logging.getLogger("vgi_rpc.rpc").exception` first), so a misconfigured worker dies loudly rather than serving in a broken state.
120
+
121
+
For pipe / unix transports the hook fires inside `RpcServer.serve(transport)`. For HTTP it fires lazily on the first request handled in the current process — this is fork-safe under pre-fork WSGI servers (gunicorn, uwsgi), so each child worker runs its own startup logic. Subprocess workers report `PIPE` because they speak Arrow IPC over the parent's stdin/stdout.
122
+
123
+
`SHM` availability is exposed via `transport_capabilities`, not the enum, so coarse transport-kind checks stay simple while workers that need zero-copy paths can still detect shared memory:
0 commit comments