Skip to content

odu: the MCP agent face — drive CI from a coding agent (juspay/odu#3, consumed here) - #1258

Merged
srid merged 6 commits into
masterfrom
atlas-odu-mcp
Jun 10, 2026
Merged

odu: the MCP agent face — drive CI from a coding agent (juspay/odu#3, consumed here)#1258
srid merged 6 commits into
masterfrom
atlas-odu-mcp

Conversation

@srid

@srid srid commented Jun 10, 2026

Copy link
Copy Markdown
Member

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-level Server:

  • Tools: 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).
  • Resources: odu://nodes + odu://log/{node} subscribable, with notifications/resources/updated — the live-push ceiling; wait_for_settle is the floor for hosts that don't wake the model on a notification.
  • In-band (dials .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.yml declares the MCP server (the nix-chrome-devtools-mcp pattern) + ships the odu-mcp launcher (nix run github:juspay/odu -- mcp; ODU_FLAKE=.#odu to 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_settle false-green, a startRun hang/exit race, a durable-log path traversal, an unbounded readFileSync, MCP request-cancellation, subscribe-URI validation, a test temp-dir leak); lowy ⇄ hickey consensus (3 fixes — drive the verdict off the STATUS_META/summarize receptacles, 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)

  • npins pins odu → master (aa0af7c, the merge commit); nix run .#odu -- mcp works through kolu's flake re-export (validated — see Evidence).
  • apm depends on juspay/odu, deploying the odu-mcp launcher and registering the odu MCP server in .mcp.json, .codex/config.toml, and opencode.json.
  • Atlas note mini-ci-vs-justci.mdx updated as-built (MCP face shipped; status / roadmap / ledger / D2; @kolu/surface-mcp considered and rejected by a Lowy+Hickey pass — consumer-local odu 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 (runwait_for_settle → green in ~9 min).

🤖 Generated with Claude Code

srid added 4 commits June 9, 2026 20:40
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.
@srid srid changed the title docs(atlas): re-center odu Phase 2 on the MCP agent face, demote the PWA odu: the MCP agent face — drive CI from a coding agent (juspay/odu#3, consumed here) Jun 10, 2026
@srid

srid commented Jun 10, 2026

Copy link
Copy Markdown
Member Author

Evidence

odu mcp served through kolu's flake re-export (nix run .#odu -- mcp) — the agent face this PR adds, speaking MCP over stdio:

$ nix run .#odu -- mcp        # in-band; dials .ci/odu.sock

→ initialize
    server: odu v0.1.0   capabilities: tools, resources
→ tools/list
    run              Start a CI run the agent can then watch and drive.
    get_nodes        Snapshot the live pipeline: every node's id, status, exit code and duration in one structured frame.
    tail_log         One node's output so far (the buffered snapshot — replays a node that already finished).
    rerun_node       Reset a node and its transitive dependents on the live DAG and reschedule them — the only mutation, no new run process.
    wait_for_settle  Block until the run settles, or — fail-fast (default) — the instant a node goes red, so you can drill into a failure without waiting for the slow lanes.
→ resources/list:           odu://nodes
→ resources/templates/list: odu://log/{node}

This is the same odu kolu's CI runs (the npins pin), now also drivable by an agent. The end-to-end loop — runwait_for_settle (fail-fast) → tail_log the red node → rerun_nodewait_for_settle — is covered by 61 unit tests over the real unix-socket transport in juspay/odu#3, and the MCP-capable odu is what posts this repo's checks (odu-on-odu CI green there).

srid added 2 commits June 10, 2026 03:25
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
srid merged commit 0471f8b into master Jun 10, 2026
14 of 15 checks passed
@srid
srid deleted the atlas-odu-mcp branch June 10, 2026 13:09
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)
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.

1 participant