odu: the MCP agent face — drive CI from a coding agent (juspay/odu#3, consumed here) - #1258
Merged
Conversation
We are a CLI tool — nobody opens a browser tab to watch a run they kicked off from their shell. Restructure 'Faces over one surface' into three named sub-faces (human TUI · agent MCP · latent browser) and flesh out MCP as the actual Phase 2 work: - tools map one-to-one onto the surface (get_nodes/tail_log/rerun_node) plus a blocking wait_for_settle — the insight that an agent needs a snapshot + a blocking 'done', not streaming (exactly what justci's #22 couldn't get) - no new authz boundary: single-operator odu mcp acts as the operator under the same ssh trust as the CLI; only a multi-client browser forces the read-observer/mutator split - .mcp.json now declares 'nix run github:juspay/odu -- mcp' (odu is its own repo; no @kolu/odu-mcp package) - demote the PWA to a latent capability the surface allows but a CLI doesn't need; D2/ledger/roadmap updated; gate-half marked shipped (Phase 1 done)
odu mcp dials .ci/odu.sock like status/logs/monitor; host selection (linux from the warm kolu-ci-* pool lease, macos from hosts.json) stays the coordinator's job, so .mcp.json is host-free and identical everywhere.
The nodes Cell maps to an MCP subscribable resource (resources/subscribe → notifications/resources/updated) for notification-aware hosts; the honest floor is wait_for_settle as a blocking pull, since many hosts don't wake the model on a notification. Same Cell, two projections; neither needs raw byte streaming. wait_for_settle is fail-fast — returns the instant a node fails, not only when the whole run settles.
Wire kolu to odu's new MCP server (juspay/odu#3) so a coding agent drives CI with structured calls: - npins: pin odu → 2997b5b (the reviewed agent-face-mcp head; the recorded revision is immutable and survives the merge). Re-pin to master with `npins update odu` after juspay/odu#3 merges. - apm: depend on juspay/odu#agent-face-mcp so odu's apm.yml MCP declaration deploys the odu-mcp `serve` launcher and registers the `odu` MCP server in .mcp.json / .codex/config.toml / opencode.json. (After the odu PR merges, drop the #ref back to `juspay/odu` and `just ai::apm-update juspay/odu`.) - nix run .#odu -- mcp validated through kolu's re-export: serves the 5 tools + the odu://nodes resource. - atlas: the MCP agent face is shipped (juspay/odu#3) and consumed here — note status/roadmap/ledger/D2 updated as-built. - changelog: Added entry.
Member
Author
Evidence
This is the same odu kolu's CI runs (the npins pin), now also drivable by an agent. The end-to-end loop — |
The deployed odu-mcp launcher fetched github:juspay/odu — unpinned master, which lacks the mcp command until a release lands and ignores the npins pin. Bump the odu pin to the launcher fix (juspay/odu@3034131) so .claude/skills/ odu-mcp/bin/serve runs the repo's own pinned .#odu. Verified: the deployed launcher serves the MCP face (tools + resources). Atlas .mcp.json example uses .#odu too.
…ay/odu odu#3 merged to master (aa0af7c). Re-pin off the now-deleted agent-face-mcp branch onto master: npins → aa0af7c, apm dep back to plain juspay/odu, and the deployed odu-mcp launcher resolves github:juspay/odu (override ODU_FLAKE=.#odu). Verified: nix run .#odu -- mcp serves the 5 tools on the master pin.
srid
added a commit
that referenced
this pull request
Jun 10, 2026
## Re-pin odu to master — `attach` rename + unified live view Bumps kolu's [odu](https://github.com/juspay/odu) pin from `aa0af7c` (the MCP merge, #1258) to master **`7fa40f3`**, picking up four merged odu PRs: - **[juspay/odu#5](juspay/odu#5 — one progress contract for `run` + `monitor` (the `-o json` / piped-plain dedup; `monitor` json regained `recipe`/`platform`/`log`). - **[juspay/odu#7](juspay/odu#7 + **[#8](juspay/odu#8 — renamed the interactive command **`monitor` → `attach`** (clean break, no alias), matching odu's "a CI runner you attach to" thesis. - **[juspay/odu#9](juspay/odu#9 — **unified the live view**: `attach` now paints `run`'s recipes×platforms matrix (one shared renderer) with a focused-node log pane, reading the lane→host map off a new fan-in `header` cell. ### What changed here - **npins** (`npins/sources.json`) → odu `7fa40f3` + new hash. - **apm** (`apm.lock.yaml` + `just ai apm-update juspay/odu`) → redeployed odu's `ci` skill, which now documents `odu attach` (`.claude/skills/ci`, `.agents/skills/ci`). The `odu-mcp` launcher and `.mcp.json` MCP-server entry are **unchanged** (the `odu mcp` command didn't move). - **Docs synced** for the rename + unification: `README.md`, `.agency/do.md`, and the `mini-ci-vs-justci` Atlas note (+ rebuilt `dist/`). Carefully left the *example node* named `monitor`, *justci's* `monitor` subcommand, and the `remote-process-monitor` lineage untouched. ### Validation `nix build .#odu .#odu-runner` green on the new pin; `nix run .#odu` usage now lists `attach`. CI will confirm end-to-end (kolu's own pipeline runs through this odu). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
srid
added a commit
that referenced
this pull request
Jun 10, 2026
Plan-of-record Atlas note for #982 — the generic `@kolu/surface/mcp` adapter (*expose any stdio-served surface as an MCP server*). The issue's framing is right: the framework is one adapter away from "every Kolu surface is also an MCP server." This note argues for **scoping it to the part that's actually a framework primitive**, because we already shipped the rest by hand (odu's `odu mcp` face, juspay/odu#3 / #1258) and learned exactly where the seam falls. ## Thesis - **The 1:1 map is the demo, not the product.** Of odu's five MCP tools, exactly *one* (`rerun_node`) is a procedure mapped 1:1; the four useful ones (`get_nodes`, `tail_log`, `wait_for_settle`, `run`) are hand-authored projections/compositions — and the surface's other procedure (`run.configure`, dangerous) is deliberately *not* a tool. "Every procedure → tool" ships the dangerous verb and misses the good ones. - **The hard part is lifecycle, not mapping.** The bulk of odu's `src/mcp/` (~1550 lines, not "one file") is `ResourcePusher` — the `resources/subscribe` teardown done right (the generation-token / detach-without-abort dance that dodges the `ERR_STREAM_DESTROYED` race). That, plus the missing **zod→JSON-Schema** bridge and composition with the already-shipped `serveOverStdio` stdout discipline, is the genuinely generic spine worth extracting. - **Two shapes, named apart:** serve a fresh spec (the notes-app demo) vs. bridge a live running surface (odu dials `.ci/odu.sock`). Pick bridge-a-live-surface as primary. - **The authz boundary every adapter inherits:** every exposed procedure is RCE for whoever connects — fine single-operator, load-bearing the moment it's multi-client. The selection gate must be **default-deny**. - **Electricity test, applied honestly:** the spine graduates (odu can delete it); the curation/guards stay hand-written. Scope #982 to the spine + gate, with the notes demo as falsifiability proof and odu's *partial* migration as the graduation receipt — not "every surface is an MCP server for free." ## Files - `docs/atlas/src/content/atlas/surface-mcp.mdx` — the new plan note (`kind: feature`, `status: proposed`), with a D2 architecture diagram of the seam. - `docs/atlas/src/content/atlas/mini-ci-vs-justci.mdx` — forward-reference from odu's MCP-face section to the generic package (and dropped the now-inaccurate "~one file" claim). - `docs/atlas/dist/` — rebuilt + staged (`ci::atlas-sync` green, host-independent). **Rendered preview:** https://htmlpreview.github.io/?https://github.com/juspay/kolu/blob/atlas-surface-mcp/docs/atlas/dist/surface-mcp.html Proposed plan — `status: proposed`, for review. Not merged. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
odu: the MCP agent face — drive CI from a coding agent
This PR adds odu's agent face and consumes it in kolu, so a coding agent (Claude Code, Codex, opencode, Gemini CLI) drives kolu's CI with structured calls instead of scraping the terminal — the live-service dividend justci's batch shape couldn't deliver (juspay/justci#22 built an MCP mode and reverted it because process-compose auto-ran every recipe on launch; a runner that owns the DAG as idle state has that separation by construction).
odu side — juspay/odu#3 ✅ merged
odu mcp— an MCP stdio server on the SDK's low-levelServer:run(start a background run and attach) ·get_nodes·tail_log(live stream, or the durable per-SHA log) ·rerun_node(the only mutation) ·wait_for_settle(fail-fast — returns the instant a node goes red, not just on full settle).odu://nodes+odu://log/{node}subscribable, withnotifications/resources/updated— the live-push ceiling;wait_for_settleis the floor for hosts that don't wake the model on a notification..ci/odu.sock), predetermines no host (pool lease / hosts.json stays the coordinator's job), no new authz (single-operator, same ssh trust as the CLI).apm.ymldeclares the MCP server (the nix-chrome-devtools-mcp pattern) + ships theodu-mcplauncher (nix run github:juspay/odu -- mcp;ODU_FLAKE=.#oduto use a repo's own pinned odu).It went through the full /be-review gauntlet: codex ⇄ Claude consensus (2 rounds, 7 findings fixed — a
wait_for_settlefalse-green, astartRunhang/exit race, a durable-log path traversal, an unboundedreadFileSync, MCP request-cancellation, subscribe-URI validation, a test temp-dir leak); lowy ⇄ hickey consensus (3 fixes — drive the verdict off theSTATUS_META/summarizereceptacles, share the node-row projection); code-police clean. A follow-up rigor/fallback audit found no stupid fallbacks. odu-on-odu CI green on both platforms.kolu side — this PR (the consumption)
aa0af7c, the merge commit);nix run .#odu -- mcpworks through kolu's flake re-export (validated — see Evidence).juspay/odu, deploying theodu-mcplauncher and registering theoduMCP server in.mcp.json,.codex/config.toml, andopencode.json.mini-ci-vs-justci.mdxupdated as-built (MCP face shipped; status / roadmap / ledger / D2;@kolu/surface-mcpconsidered and rejected by a Lowy+Hickey pass — consumer-localodu mcp). Changelog entry under Added.Dogfood
The MCP-capable odu runs kolu's own CI: a full two-platform run via
ci/pu/run.sh(26/26 green), and an agent-driven linux run through this very MCP server (run→wait_for_settle→ green in ~9 min).🤖 Generated with Claude Code