Simple in-pod sidecar mode: wrap a co-located non-MCP process with spec mode #59

Closed
opened 2026-08-14 02:15:13 +00:00 by coilyco-ops · 1 comment
Member

🤖 Filed by Claude Code on Kai's behalf.

Filed by Olaf (OPS) from coilyco-bridge/deploy. Kai's call from a design conversation on 2026-08-13; consumer context is coilyco-bridge/deploy#495.

The ask

A first-class, minimal way to run a plain process beside the runtime in the same pod and wrap its HTTP surface with ordinary spec mode.

Why the existing path is not quite it

runtime.mode: upstream plus extraContainers already co-locates a container, and --connect-timeout already handles warmup. But that path is built for an upstream that is already MCPserve-upstream snapshots an upstream tool list and proxies allowlisted names.

The consumer case here is different and lighter: the co-located process speaks plain HTTP JSON, not MCP. Concretely, deploy#495 wants a SQLite dataset (GeoNames gazetteer, Natural Earth, OWID and friends) served over a local JSON API and wrapped with can get grants against localhost. Datasette is the candidate. The value is that it removes an entire class of external dependency: no rate limits, no third-party uptime, no per-request caching problem, and restrict still bounds the surface.

serve-upstream cannot express that, because there is no upstream MCP to snapshot. Spec mode can, because base-url is just a URL.

This may be much smaller than it sounds — please check first

The honest version of this ask is "make it work and document it," not necessarily "build a mode." I could not determine from README.md, docs/FEATURES.md, or docs/chart.md whether extraContainers is available in runtime.mode: spec or only in upstream mode. The docs only ever discuss it under upstream mode.

If it already works in spec mode, then this whole issue is likely:

  • a values example (examples/sidecar.values.yaml),
  • a line in the chart docs saying this is a supported shape,
  • and a decision about readiness ordering.

If it does not, the chart change is probably still small.

Determining which of those it is should be the first step, not the implementation.

Readiness ordering is the one real design question

Spec mode does not connect at startup — it resolves base-url per request — so unlike upstream mode there is no crash-loop risk from a slow sidecar. A request arriving before the sidecar is up simply errors.

That is arguably acceptable, but it means the pod can report ready while its data plane is not. Worth deciding deliberately: either a readiness gate that waits on the sidecar, or an explicit statement that early requests fail and that is fine.

Acceptance

  • Whether extraContainers works in runtime.mode: spec today is determined and recorded on this issue before any code is written.
  • A guardfile with base-url pointing at a co-located container serves tools that reach it.
  • A reference values file demonstrates the shape.
  • Readiness behaviour is documented, whichever way it is decided.
  • No change to serve-upstream semantics.

Note on scope

This does not ask for multi-upstream single-spec, which Kai has ruled out as intentional (deploy#495). One guardfile, one base-url, which here happens to be localhost.

> 🤖 Filed by Claude Code on Kai's behalf. **Filed by Olaf (OPS)** from `coilyco-bridge/deploy`. Kai's call from a design conversation on 2026-08-13; consumer context is https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/495. ## The ask A first-class, minimal way to run a plain process beside the runtime in the same pod and wrap its HTTP surface with ordinary spec mode. ## Why the existing path is not quite it `runtime.mode: upstream` plus `extraContainers` already co-locates a container, and `--connect-timeout` already handles warmup. But that path is built for an upstream that is **already MCP** — `serve-upstream` snapshots an upstream tool list and proxies allowlisted names. The consumer case here is different and lighter: the co-located process speaks **plain HTTP JSON, not MCP**. Concretely, deploy#495 wants a SQLite dataset (GeoNames gazetteer, Natural Earth, OWID and friends) served over a local JSON API and wrapped with `can get` grants against `localhost`. Datasette is the candidate. The value is that it removes an entire class of external dependency: no rate limits, no third-party uptime, no per-request caching problem, and `restrict` still bounds the surface. `serve-upstream` cannot express that, because there is no upstream MCP to snapshot. Spec mode can, because `base-url` is just a URL. ## This may be much smaller than it sounds — please check first **The honest version of this ask is "make it work and document it," not necessarily "build a mode."** I could not determine from `README.md`, `docs/FEATURES.md`, or `docs/chart.md` whether `extraContainers` is available in `runtime.mode: spec` or only in `upstream` mode. The docs only ever discuss it under upstream mode. If it already works in spec mode, then this whole issue is likely: - a values example (`examples/sidecar.values.yaml`), - a line in the chart docs saying this is a supported shape, - and a decision about readiness ordering. If it does not, the chart change is probably still small. **Determining which of those it is should be the first step, not the implementation.** ## Readiness ordering is the one real design question Spec mode does not connect at startup — it resolves `base-url` per request — so unlike upstream mode there is no crash-loop risk from a slow sidecar. A request arriving before the sidecar is up simply errors. That is arguably acceptable, but it means the pod can report ready while its data plane is not. Worth deciding deliberately: either a readiness gate that waits on the sidecar, or an explicit statement that early requests fail and that is fine. ## Acceptance - Whether `extraContainers` works in `runtime.mode: spec` today is **determined and recorded on this issue** before any code is written. - A guardfile with `base-url` pointing at a co-located container serves tools that reach it. - A reference values file demonstrates the shape. - Readiness behaviour is documented, whichever way it is decided. - No change to `serve-upstream` semantics. ## Note on scope This does not ask for multi-upstream single-spec, which Kai has ruled out as intentional (deploy#495). One guardfile, one `base-url`, which here happens to be `localhost`.
Author
Member

Determination first, as the issue required: extraContainers already works in runtime.mode: spec.

chart/templates/deployment.yaml appends .Values.extraContainers to the pod's container list at lines 124-126 with no mode condition. Only three blocks are gated on $mode: the runtime args (63-78), the /spec volumeMount (104-109), and the volumes (127-132). Nothing about co-locating a container is mode-specific.

So this was the small version you hoped for. Landed in 17351e2:

  • examples/sidecar.mcp.kdl - a Datasette-shaped gazetteer wrapped over 127.0.0.1:8001.
  • examples/sidecar.values.yaml - the pod shape.
  • ward exec helm-template-sidecar - a tracked verb rendering it, so the shape is covered by the repo's own validation rather than only by prose.
  • A "Sidecar shape" section in docs/chart.md stating that extraContainers is not mode-gated, and why the two modes co-locate for different reasons.

Only upstream mode was ever documented, which is what made a supported shape look unavailable.

Readiness - decided rather than left implicit. You were right that this is the one real question, and right about the direction. Spec mode never connects at startup, so there is no crash-loop risk; the cost is the opposite failure, and it is worse than it first sounds: ward-mcp binds immediately and answers tools/list correctly while the sidecar is still loading. The pod reports Ready with its data plane down.

The reference values gate readiness on the sidecar's own port. Containers share a network namespace, so a probe can target it directly. Liveness stays on ward-mcp, so a wedged sidecar fails readiness and stops taking traffic rather than restarting a healthy runtime out from under itself.

Accepting the default and letting early requests fail is defensible for a fast-starting sidecar. Not chosen as the reference, because "fails briefly after every rollout" is the kind of error that gets attributed to the wrong component.

One thing worth knowing before building on this. The wrap grammar requires an auth block and has no none scheme, so a loopback upstream needing no credential still has to name one. The example uses a pod-local shared token. Relaxing that is a umbra grammar change rather than a runtime one, so it is not taken here - flagging it because it is a small surprise waiting for whoever writes the deploy#495 guardfile.

serve-upstream semantics untouched, and multi-upstream single-spec not touched either.

Caveat on the evidence: helm is not available in this session, so the determination is from reading the template with line citations, not from a rendered manifest. ward exec helm-template-sidecar is the one-command confirmation.

**Determination first, as the issue required: `extraContainers` already works in `runtime.mode: spec`.** `chart/templates/deployment.yaml` appends `.Values.extraContainers` to the pod's container list at lines 124-126 with **no mode condition**. Only three blocks are gated on `$mode`: the runtime args (63-78), the `/spec` volumeMount (104-109), and the volumes (127-132). Nothing about co-locating a container is mode-specific. So this was the small version you hoped for. Landed in 17351e2: * `examples/sidecar.mcp.kdl` - a Datasette-shaped gazetteer wrapped over `127.0.0.1:8001`. * `examples/sidecar.values.yaml` - the pod shape. * `ward exec helm-template-sidecar` - a tracked verb rendering it, so the shape is covered by the repo's own validation rather than only by prose. * A "Sidecar shape" section in `docs/chart.md` stating that `extraContainers` is not mode-gated, and why the two modes co-locate for different reasons. Only upstream mode was ever documented, which is what made a supported shape look unavailable. **Readiness - decided rather than left implicit.** You were right that this is the one real question, and right about the direction. Spec mode never connects at startup, so there is no crash-loop risk; the cost is the opposite failure, and it is worse than it first sounds: ward-mcp binds immediately and answers `tools/list` *correctly* while the sidecar is still loading. The pod reports Ready with its data plane down. The reference values gate readiness on the sidecar's own port. Containers share a network namespace, so a probe can target it directly. Liveness stays on ward-mcp, so a wedged sidecar fails readiness and stops taking traffic rather than restarting a healthy runtime out from under itself. Accepting the default and letting early requests fail is defensible for a fast-starting sidecar. Not chosen as the reference, because "fails briefly after every rollout" is the kind of error that gets attributed to the wrong component. **One thing worth knowing before building on this.** The wrap grammar requires an `auth` block and has no `none` scheme, so a loopback upstream needing no credential still has to name one. The example uses a pod-local shared token. Relaxing that is a umbra grammar change rather than a runtime one, so it is not taken here - flagging it because it is a small surprise waiting for whoever writes the deploy#495 guardfile. `serve-upstream` semantics untouched, and multi-upstream single-spec not touched either. **Caveat on the evidence:** helm is not available in this session, so the determination is from reading the template with line citations, not from a rendered manifest. `ward exec helm-template-sidecar` is the one-command confirmation.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/mcp-beaver#59
No description provided.