No description
  • Python 96.6%
  • Makefile 2.6%
  • Dockerfile 0.4%
  • Shell 0.4%
Find a file
Kai Siren 9191959615
Some checks are pending
build-publish-deploy / test (push) Waiting to run
build-publish-deploy / deploy (push) Blocked by required conditions
TruffleHog / Scan for secrets (push) Waiting to run
fix(agent-channel): serve MCP off a middleware-free carrier app
The MCP streamable-HTTP transport returned empty responses through the
backend's two BaseHTTPMiddleware middlewares - a long-open stream does
not survive that stack, which is why mcporter timed out at 30s. The
MCP server now mounts on its own bare FastAPI carrier with no
middleware, and a pure-ASGI passthrough middleware diverts /mcp to the
carrier before the BaseHTTPMiddleware stack runs. Same pattern eco-mcp-app
uses: keep streamed MCP transport clear of BaseHTTPMiddleware.

closes #87

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audit-log: coily://1779452888/AGPE7KCE - coily git add backend/main.py
Audit-log: coily://1779452891/AGPE7KCQ - coily ops aws sts get-caller-identity
Audit-log: coily://1779452891/AGPE7KCT - coily ops aws ssm get-parameter
Audit-log: coily://1779452892/AGPE7KCT - coily git commit
Audit-log: coily://1779452897/AGPE7KDJ - coily git push
Audit-log: coily://1779453303/AGPE7LU3 - coily ops aws ssm get-parameter
Audit-log: coily://1779454253/AGPE7PI2 - coily exec test
Audit-log: coily://1779454274/AGPE7PLK - coily git add backend/main.py tests/test_modes.py
2026-05-22 05:51:14 -07:00
.claude fix(deploy): fully-qualify the sideloaded image ref (#73) 2026-05-20 21:43:18 -07:00
.coily feat: restructure backend into a generic modes framework 2026-05-20 23:48:58 -07:00
.github/workflows feat: restructure backend into a generic modes framework 2026-05-20 23:48:58 -07:00
.vscode workon otel 2025-03-26 00:12:50 -07:00
backend fix(agent-channel): serve MCP off a middleware-free carrier app 2026-05-22 05:51:14 -07:00
deploy fix: match db StatefulSet storage to the live 5Gi PVC 2026-05-22 02:01:52 -07:00
docs feat: restructure backend into a generic modes framework 2026-05-20 23:48:58 -07:00
tests fix(agent-channel): serve MCP off a middleware-free carrier app 2026-05-22 05:51:14 -07:00
todos docs(todos): drop archived claude-code-pulse from sentry todos 2026-05-16 15:27:48 -07:00
.dockerignore docker time 2025-04-03 20:47:20 -07:00
.gitignore chore: commit coily lockdown baseline, gitignore host-local Claude state 2026-04-24 15:42:18 -07:00
.pre-commit-config.yaml feat(agent-channel): content-negotiate the onboarding view 2026-05-22 03:29:30 -07:00
.python-version cleanup: align backend with sister repos 2026-04-23 04:13:45 -07:00
AGENTS.md rename: update cross-repo refs after coilyco-ai to agentic-os-kai 2026-05-15 02:27:14 -07:00
CLAUDE.md Convert CLAUDE.md symlink to @AGENTS.md import 2026-04-23 13:35:34 -07:00
coily.yaml deploy: rename config.yml to coily.yaml 2026-05-13 22:49:48 -07:00
Dockerfile Drop requirements.txt, use uv.lock directly in Docker 2026-05-14 06:20:22 -07:00
Makefile fix(deploy): fully-qualify the sideloaded image ref (#73) 2026-05-20 21:43:18 -07:00
pyproject.toml feat(agent-channel): expose the channel as an MCP server at /mcp 2026-05-22 05:28:08 -07:00
README.md feat: restructure backend into a generic modes framework 2026-05-20 23:48:58 -07:00
uv.lock feat(agent-channel): expose the channel as an MCP server at /mcp 2026-05-22 05:28:08 -07:00

backend

A generic data-accessibility framework. An internal operational backend with Postgres behind it, exposing a small set of uniform modes, each a FastAPI router with a generic endpoint and a self-documenting sentinel record. Internal-only - reachable on the tailnet via an in-Pod Tailscale sidecar, no public ingress. Design: coilysiren/backend#77, coilysiren/backend#65, coilysiren/agentic-os-kai#657.

Deploys to the k3s homelab via the canonical rig in infrastructure/docs/k3s-deploy-notes.md.

Modes

Each mode lives in backend/modes/, owns one table, and ships an APIRouter plus an async init(pool) that creates its schema and upserts a sentinel row. Adding a mode is a new module plus an entry in ALL_MODES - nothing else changes.

  • health - GET /health: DB connectivity, the mounted modes, and every sentinel. The only unauthed route - it is the liveness probe.
  • document - append-only jsonb document store keyed by (namespace, key). POST /document, GET /document/{namespace}, GET /document/{namespace}/{key}, DELETE /document/{namespace}/{key}.
  • queue - SELECT ... FOR UPDATE SKIP LOCKED work queue. POST /queue/{namespace} enqueue, POST /queue/{namespace}/claim claim, DELETE /queue/{namespace}/{id} ack. visible_at carries enqueue delay and post-claim visibility timeout.
  • sql - generic relational mode. POST /sql/tables creates a real typed table sql_<name> from a column spec, GET /sql/tables lists the registry, POST /sql/tables/{name}/rows and GET /sql/tables/{name}/rows CRUD its rows. Strict identifier regex plus a closed type allowlist - the "dispatch a new table type from mobile" surface.
  • file - temp-tier file storage. POST /files/temp writes a raw body to an ephemeral emptyDir, returns an id. Write-only by design - no read route in v1.

Sentinel pattern

A shared sentinels(mode, shape jsonb, note, created_at) table. Each mode upserts one row on init describing its record shape - the ".keep-of-schemas" exemplar that keeps the framework self-documenting. GET /health returns them all.

Auth

Every mode route except /health requires an Authorization: Bearer <DATASTORE_TOKEN> header. The token lives in AWS SSM at /coilysiren/backend/datastore-token. Auth fails closed - with no token configured the routes return 503 rather than open up.

Install

brew install uv jq
brew install --cask docker

Environment

Create .env:

DATASTORE_TOKEN=dev-token              # bearer token every mode route validates
DATABASE_URL=postgresql://backend:backend@localhost:5432/backend
FILE_TEMP_DIR=/tmp/backend-files       # temp tier for the file mode
OTEL_SDK_DISABLED=true

DATABASE_URL can be left unset and assembled from PGHOST / PGUSER / PGPASSWORD / PGDATABASE / PGPORT instead, which is how the k8s manifest injects it.

A local Postgres for development:

docker run -d --name backend-db -p 5432:5432 \
  -e POSTGRES_USER=backend -e POSTGRES_PASSWORD=backend -e POSTGRES_DB=backend \
  postgres:17

Run

make build-native    # uv lock + uv sync
make run-native      # uvicorn on :4000

curl -s http://localhost:4000/health | jq

curl -s -X POST http://localhost:4000/document \
  -H "Authorization: Bearer dev-token" \
  -H "Content-Type: application/json" \
  -d '{"namespace":"ci-status","key":"demo","payload":{"status":"ok"}}' | jq

curl -s http://localhost:4000/document/ci-status/demo \
  -H "Authorization: Bearer dev-token" | jq

Test

make test

Pure tests run without a database. The DB-integration tests are skipped unless BACKEND_TEST_DATABASE_URL points at a throwaway Postgres.

Commands

Dev commands are declared in .coily/coily.yaml. Run them as coily exec <verb>.

See also

  • AGENTS.md - agent-facing operating rules.
  • docs/FEATURES.md - inventory of what ships today.
  • .coily/coily.yaml - allowlisted commands. Agents route through coily, not bare make / uv / python / npm / cargo / dotnet.

Cross-reference convention from coilysiren/agentic-os-kai#313.