Hi. This is a request for comment on a small extension seam. I'm opening it as an issue first (per the README) so we can agree on the shape before I write any PR.
Problem
Right now, if you want to extend the server you end up editing core files (coworker/server/app.py, coworker/server/manager.py, coworker/secrets.py), because there is no supported extension point for three fairly common needs:
- wrapping or observing the per-session engine after it is built
- mounting extra routes behind the existing sidecar-token middleware
- observing credential reads
Each of those edits becomes a merge headache against a fast-moving core. It is the same friction #33 raises about the manager.py and integration_tools.py god objects, and it shows up piecemeal in other places too: #226 adds a guard_middleware param to TurnEngine, and #403 wants pluggable provider routing. Three efforts, three different injection points.
Proposal: three small optional hooks
All optional, default None, with behavior unchanged when nothing is registered, and no new dependencies. They follow patterns the codebase already uses (TurnEngine.interrupt_hooks, SessionManager on_use, and the TurnEngine(provider=, registry=) injection).
A. Engine lifecycle hook. SessionManager(engine_hooks=[...]), called as hook(engine, session_id) right after the engine is built. A hook can return a wrapped engine, or None to just observe.
B. App extension. create_app(manager, extensions=[...]), where each extension can mount(app), declare tokenless_paths(), and run an optional on_startup(app) validator. It could be namespaced under /ext/ to avoid route collisions if you would rather keep it narrow.
C. Secret-access observer. SecretStore(on_access=callable), called with the profile string only, so it stays content-free, and where a raising observer fails the read closed. A constructor param rather than global state.
This lines up with #33. Engine construction and route registration get thin, documented facades instead of growing inline, with no behavior change, covered by the existing tests.
Its guard_middleware runs per tool call (evaluate plus track_start/track_end for fan-out counting), which is a finer granularity than hook A. If you want, the same PR could add a fourth optional tool-call middleware seam, TurnEngine(tool_middleware=[...]), so #226's GuardMiddleware becomes a registered entry instead of a core param. That is only worth doing if you want #226 generalized, and I do not want to hold it up either way.
What I am asking
A yes or no on the shape, and whether you would want that fourth hook. If it is welcome I will open a PR with the hooks, unit tests for hook invocation, a demonstration that the route table, engine build, and secret reads are unchanged when nothing is registered, and before/after notes. Mark it experimental if you like, no stability promise while the beta is settling.
Happy to cut it down to whatever subset you would actually merge.
Hi. This is a request for comment on a small extension seam. I'm opening it as an issue first (per the README) so we can agree on the shape before I write any PR.
Problem
Right now, if you want to extend the server you end up editing core files (coworker/server/app.py, coworker/server/manager.py, coworker/secrets.py), because there is no supported extension point for three fairly common needs:
Each of those edits becomes a merge headache against a fast-moving core. It is the same friction #33 raises about the manager.py and integration_tools.py god objects, and it shows up piecemeal in other places too: #226 adds a guard_middleware param to TurnEngine, and #403 wants pluggable provider routing. Three efforts, three different injection points.
Proposal: three small optional hooks
All optional, default None, with behavior unchanged when nothing is registered, and no new dependencies. They follow patterns the codebase already uses (TurnEngine.interrupt_hooks, SessionManager on_use, and the TurnEngine(provider=, registry=) injection).
A. Engine lifecycle hook. SessionManager(engine_hooks=[...]), called as hook(engine, session_id) right after the engine is built. A hook can return a wrapped engine, or None to just observe.
B. App extension. create_app(manager, extensions=[...]), where each extension can mount(app), declare tokenless_paths(), and run an optional on_startup(app) validator. It could be namespaced under /ext/ to avoid route collisions if you would rather keep it narrow.
C. Secret-access observer. SecretStore(on_access=callable), called with the profile string only, so it stays content-free, and where a raising observer fails the read closed. A constructor param rather than global state.
This lines up with #33. Engine construction and route registration get thin, documented facades instead of growing inline, with no behavior change, covered by the existing tests.
On #226
Its guard_middleware runs per tool call (evaluate plus track_start/track_end for fan-out counting), which is a finer granularity than hook A. If you want, the same PR could add a fourth optional tool-call middleware seam, TurnEngine(tool_middleware=[...]), so #226's GuardMiddleware becomes a registered entry instead of a core param. That is only worth doing if you want #226 generalized, and I do not want to hold it up either way.
What I am asking
A yes or no on the shape, and whether you would want that fourth hook. If it is welcome I will open a PR with the hooks, unit tests for hook invocation, a demonstration that the route table, engine build, and secret reads are unchanged when nothing is registered, and before/after notes. Mark it experimental if you like, no stability promise while the beta is settling.
Happy to cut it down to whatever subset you would actually merge.