Skip to content

feat(adapters): HTTP sidecar for language-agnostic route/compact (#674, #675, #676, #677, #678)#703

Open
dgenio wants to merge 1 commit into
mainfrom
claude/github-issues-triage-8vhbt2
Open

feat(adapters): HTTP sidecar for language-agnostic route/compact (#674, #675, #676, #677, #678)#703
dgenio wants to merge 1 commit into
mainfrom
claude/github-issues-triage-8vhbt2

Conversation

@dgenio

@dgenio dgenio commented Jun 15, 2026

Copy link
Copy Markdown
Owner

Expose the deterministic router and the context firewall over a small,
versioned HTTP/JSON API so non-Python agents can use them without embedding
Python (umbrella #427). Built on the Python standard library (http.server) with
no new dependency, reusing the same sync Router and compact_tool_result facade
as the in-process API.

https://claude.ai/code/session_01XaxkTHgVAusnHBUH4ttsVq

#675, #676, #677, #678)

Expose the deterministic router and the context firewall over a small,
versioned HTTP/JSON API so non-Python agents can use them without embedding
Python (umbrella #427). Built on the Python standard library (http.server) with
no new dependency, reusing the same sync Router and compact_tool_result facade
as the in-process API.

- sidecar_contract.py (#674): RouteRequest/RouteResponse/CompactRequest/
  CompactResponse/SidecarError dataclasses + SIDECAR_API_VERSION; versioned
  JSON Schemas + example payloads under schemas/sidecar/v1/.
- sidecar.py (#675/#676): SidecarConfig + transport-free SidecarApp.dispatch
  over /v1/route, /v1/compact, /v1/health; optional bearer-token auth,
  per-client rate limiting (reuses gateway_controls.RateLimiter), body-size
  cap, and typed SidecarError responses; never raises across the boundary.
- _sidecar_http.py (#675): stdlib ThreadingHTTPServer binding; serve_api +
  make_sidecar_server.
- CLI: `contextweaver serve-api` command.
- Clients (#677): examples/sidecar_demo.py (Python, runs under make example)
  and examples/sidecar/client.ts (TypeScript, dependency-free).
- Tests (#675/#676/#678): contract round-trip + schema validation, dispatch
  happy/invalid/auth/rate-limit/body-cap, end-to-end over a real server, and a
  concurrency load smoke. Non-gating `make sidecar-smoke` CI step.
- Docs: docs/sidecar.md, AGENTS.md module map, CHANGELOG, public-API manifest.

https://claude.ai/code/session_01XaxkTHgVAusnHBUH4ttsVq
Copilot AI review requested due to automatic review settings June 15, 2026 20:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces an HTTP sidecar mode (contextweaver serve-api) that exposes the deterministic router and context firewall over a small versioned HTTP/JSON surface (/v1/route, /v1/compact, /v1/health) so non-Python agents can consume these primitives without embedding Python.

Changes:

  • Add a versioned sidecar wire contract (dataclasses + published JSON Schemas/examples) and a transport-free dispatcher with auth, rate limiting, and body-size caps.
  • Provide a stdlib http.server ThreadingHTTPServer binding, CLI wiring, docs, and client examples (Python + TypeScript).
  • Add unit + end-to-end tests and a non-gating CI smoke step.

Reviewed changes

Copilot reviewed 29 out of 29 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/test_sidecar.py Transport-free SidecarApp.dispatch test suite (route/compact/auth/rate limit/body cap).
tests/test_sidecar_http.py End-to-end HTTP transport tests + concurrency smoke using urllib.
tests/test_sidecar_contract.py Contract round-trip tests + schema/example validation.
src/contextweaver/adapters/sidecar.py Transport-agnostic dispatcher + auth/rate-limit/body-size enforcement for sidecar endpoints.
src/contextweaver/adapters/sidecar_contract.py Sidecar wire dataclasses + to_dict/from_dict parsing/validation helpers.
src/contextweaver/adapters/_sidecar_http.py Stdlib ThreadingHTTPServer handler that bridges HTTP requests to SidecarApp.dispatch.
src/contextweaver/adapters/init.py Public re-exports for sidecar APIs and contract types.
src/contextweaver/main.py Adds contextweaver serve-api CLI command wiring for the sidecar server.
scripts/module_size_baseline.json Updates module-size baseline to include adapters/__init__.py.
schemas/sidecar/v1/route_response.schema.json JSON Schema for RouteResponse.
schemas/sidecar/v1/route_request.schema.json JSON Schema for RouteRequest.
schemas/sidecar/v1/compact_response.schema.json JSON Schema for CompactResponse.
schemas/sidecar/v1/compact_request.schema.json JSON Schema for CompactRequest.
schemas/sidecar/v1/error.schema.json JSON Schema for sidecar error payloads.
schemas/sidecar/v1/examples/route_response.json Example RouteResponse payload.
schemas/sidecar/v1/examples/route_request.json Example RouteRequest payload.
schemas/sidecar/v1/examples/compact_response.json Example CompactResponse payload.
schemas/sidecar/v1/examples/compact_request.json Example CompactRequest payload.
schemas/sidecar/v1/examples/error.json Example sidecar error payload.
mkdocs.yml Adds sidecar docs page to MkDocs nav.
Makefile Adds sidecar-smoke target and runs the demo under make example.
examples/sidecar/README.md Sidecar client examples README (Python + TypeScript usage).
examples/sidecar/client.ts Dependency-free TypeScript client using fetch.
examples/sidecar_demo.py Python demo that starts the server in-process and drives it over HTTP.
docs/sidecar.md Sidecar documentation (endpoints, contract, clients, security notes).
CHANGELOG.md Announces the new sidecar feature and public surface additions.
api/public_api.txt Updates public API manifest for new sidecar exports.
AGENTS.md Updates module map and documents sidecar modules.
.github/workflows/ci.yml Adds non-gating “Sidecar HTTP smoke” CI step.

Comment on lines +141 to +150
normalized = {k.lower(): v for k, v in headers.items()}
route = (method.upper(), path.rstrip("/") or path)

if route == ("GET", "/v1/health"):
return 200, self._health()

if path not in ("/v1/route", "/v1/compact"):
return self._error("NOT_FOUND", f"unknown path {path!r}")
if method.upper() != "POST":
return self._error("METHOD_NOT_ALLOWED", f"{method} not allowed on {path}")
Comment on lines +248 to +262
def _check_rate_limit(self, client_id: str) -> tuple[int, dict[str, Any]] | None:
if self.config.rate_limit is None:
return None
limiter = self._limiters.get(client_id)
if limiter is None:
policy = RateLimitPolicy(per_meta_tool={_RATE_BUCKET: self.config.rate_limit})
limiter = RateLimiter(policy, clock=self.clock)
self._limiters[client_id] = limiter
decision = limiter.check(_RATE_BUCKET)
if decision.allowed:
return None
details: dict[str, Any] = {"scope": decision.scope}
if decision.retry_after is not None:
details["retry_after"] = round(decision.retry_after, 3)
return self._error("RATE_LIMITED", "rate limit exceeded", retryable=True, details=details)
Comment on lines +90 to +96
def _write_json(self, status: int, payload: dict[str, Any]) -> None:
body = json.dumps(payload).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
Comment on lines +287 to +295
@classmethod
def from_dict(cls, data: dict[str, Any]) -> SidecarError:
"""Deserialise from the §3.4 JSON shape."""
return cls(
code=data["error"],
message=data.get("message", ""),
retryable=bool(data.get("retryable", False)),
details=dict(data.get("details", {})),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants