# AGENTS.md
<!-- Generated by APM CLI from distributed .apm/ primitives -->
<!-- Build ID: 30057893e7e6 -->
<!-- APM Version: 0.28.0 -->

## Files matching `packages/{surface,surface-app,surface-mcp,surface-daemon,surface-daemon-supervisor,surface-remote,surface-map}/**`

## Surface public-API changes must update the Reference docs in the same PR

The `@kolu/surface*` packages each have a **Reference page** in the four-quadrant
Surface docs at `website/src/content/surface/`. Those pages are the austere,
authoritative record of the public API — they are only trustworthy if they move
with the code.

**Any public-API change** to one of these packages — an exported type or function
signature, the Effect RPC contract (the `defineSurface` spec, the wire tags an
`RpcGroup` mints from it, a member's payload/success/error `Schema`), package
`exports`, a member added to or
removed from `defineSurface`, or any runtime behaviour a documented consumer
wires against — **must update the matching Reference page in the same PR.**

| Package | Reference page |
| --- | --- |
| `@kolu/surface` | `website/src/content/surface/ref-surface.mdx` |
| `@kolu/surface-app` | `website/src/content/surface/ref-surface-app.mdx` |
| `@kolu/surface-mcp` | `website/src/content/surface/ref-surface-mcp.mdx` |
| `@kolu/surface-daemon` | `website/src/content/surface/ref-surface-daemon.mdx` |
| `@kolu/surface-daemon-supervisor` | `website/src/content/surface/ref-surface-supervisor.mdx` |
| `@kolu/surface-remote` | `website/src/content/surface/ref-surface-remote.mdx` |
| `@kolu/surface-map` | `website/src/content/surface/ref-surface-map.mdx` |

- Update the affected entries (names, signatures, tables, snippets) so the page describes the new surface, not the old one. If the change touches an invariant, update `surface-daemon-invariants.mdx` or the relevant Explanation page too.
- "Done" is not done until the page matches the shipped API. This is the mirror, on the docs side, of the drishti-consumer gate in the sibling surface rule — the same "the record must not lie" discipline that the docs themselves are built on.
- Purely internal changes (private helpers, tests, comments, refactors with no observable API delta) do not need a docs update.
- A new export that a consumer is meant to use is an API change: add it to the Reference page. If the package's README changes shape, keep the ~15-line README (identity + install + one snippet + links into the quadrants) pointing at the Reference page rather than duplicating it.

## Files matching `packages/{surface,surface-app,surface-remote,surface-daemon,surface-daemon-supervisor}/**`

## Surface libraries are shared — mirror API changes to drishti

`@kolu/surface` and its sibling libraries (`@kolu/surface-app`, `@kolu/surface-remote`, and — since drishti's durable agent landed (UW3) — `@kolu/surface-daemon` and `@kolu/surface-daemon-supervisor`) are not kolu-internal: they are consumed by [`drishti`](https://github.com/srid/drishti) as well as kolu. Drishti's daemon serves the frozen control-core fragment, rides `daemonHome`/`daemonMain`/`frontDaemonOverStdio`, converges via `convergeAdmit`/`probeDaemonIdentityFrom`, and imports `./upgrade-window.testlib` — the daemon packages are production surface for it, gate file included.

**Any API-facing change** to `packages/surface/`, `packages/surface-app/`, `packages/surface-remote/`, `packages/surface-daemon/`, or `packages/surface-daemon-supervisor/` — exported types, function signatures, the Effect RPC contract (the `defineSurface` spec, the wire tags an `RpcGroup` mints from it, a member's payload/success/error `Schema`), package `exports`, runtime behaviour a consumer depends on, or anything else that changes how a downstream consumer wires these packages — **requires a corresponding PR to [`github.com/srid/drishti`](https://github.com/srid/drishti) that updates drishti for the change and passes full CI.**

- Open the drishti PR before (or alongside) merging the kolu change, and link the two PRs to each other. **Cut and keep the pair branch on CURRENT drishti master**: `git fetch origin` and branch off `origin/master` at open — never a stale local `master` — and merge `origin/master` into the pair branch again at **every** repin, confirming GitHub reports it `MERGEABLE` before calling it ready. The recorded failure: a pair PR (drishti#107) cut off a stale base reached the human CONFLICTING — unmergeable at the exact moment it was needed.
- The drishti PR is not optional and "done" is not "done" until drishti's CI is green against the new surface API.
- **The gate is only satisfied against the *final* kolu HEAD — re-validate it after any post-gate edit that touches the surface API.** A drishti CI green pinned to a pre-gauntlet SHA is **stale**: the review gauntlet (lens/debate/police) and CI fix-commits routinely reshape the shared API *after* the gate first passed — e.g. a gauntlet fix dropping a parameter (`pumpRemoteSurface`'s `makeSink` client arg) silently breaks drishti's consumer (`(_client, {seq})` → `({seq})`), which a gate validated at the earlier SHA never caught. Whenever the surface API moves after the gate was last green, bump the drishti PR's pin to the final kolu HEAD and re-confirm its CI is green there before calling it done.
- Purely internal changes (private helpers, tests, comments, refactors with no observable API delta) do not need a drishti PR.

## The third consumer — odu rides a loose pin, and pays with a verdict

[`odu`](https://github.com/juspay/odu) also consumes `@kolu/surface*`, but **deliberately loosely**: it pins kolu by npins and absorbs changes at its own pin bumps — there is **no per-PR odu pair** (three-repo lockstep was considered and rejected; drishti is the tight twin because it tracks kolu master continuously). The recorded failure this section exists to prevent: a whole campaign of surface changes accumulated with **zero odu-impact accounting**, so nobody knew what the eventual bump would break until it was scheduled.

- **Every PR the drishti gate applies to also posts an ODU-IMPACT VERDICT in its body** — one of `breaks-at-bump` / `adoption-opportunity` / `none`, each grounded by an actual grep of odu's tree at its **pinned** kolu SHA for consumption of the changed exports (a verdict without the grep is hollow).
- **Any non-`none` verdict is appended as a line item to the standing ledger — [odu#43](https://github.com/juspay/odu/issues/43)** — naming the kolu PR, the affected odu site, and the verdict. The ledger, not memory, is what the next pin bump drains.
- **A `breaks-at-bump` item makes the next bump schedulable work, not someday-work** — the coordinator surfaces it; the bump PR's body checks off every drained line item.

---
*This file was generated by APM CLI. Do not edit manually.*
*To regenerate: `apm compile`*
