Skip to content

feat(compass-agent): migrate off the vendored oh-my-pi subtree onto @oh-my-pi/* 18.0.11 (RIG-2407) - #924

Open
rigel-mintaka wants to merge 3 commits into
mainfrom
compass-repo/rig-2407-sdk-migration
Open

feat(compass-agent): migrate off the vendored oh-my-pi subtree onto @oh-my-pi/* 18.0.11 (RIG-2407)#924
rigel-mintaka wants to merge 3 commits into
mainfrom
compass-repo/rig-2407-sdk-migration

Conversation

@rigel-mintaka

Copy link
Copy Markdown
Contributor

Repoints compass-agent from the vendored forks/oh-my-pi/ subtree onto the published upstream SDK, @oh-my-pi/{pi-agent-core,pi-ai,pi-coding-agent,omptype}@^18.0.11. This is PR1 of 3: runtime only. The generator repoint (PR2) and the actual deletion of forks/oh-my-pi/ (PR3) stack on top, so the subtree is still present and still the generator's schema source after this lands.

Why upstream, not the Rigel fork

forks/README.md:151-155 records the subtree's Sealed changes as NONE — byte-identical to upstream, with one sealed-added forks/oh-my-pi/moon.yml. The forks/oh-my-pi/patches/ dir is upstream's own patchedDependencies, not ours. With no compass-specific patches to preserve there is nothing to carry, so this points at upstream latest and drops the vendoring rather than tracking a rebrand.

bunfig.toml is untouched: 18.0.11 published 2026-08-29 and is already past the 5-day minimumReleaseAge soak, so no exemption entry was needed.

The five API deltas, each fixed on the real contract

  1. arktype@oh-my-pi/omptype/ark. The direct arktype dependency is dropped; omptype is an ArkType-compatible lazy-JIT implementation whose /ark facade is purpose-built for this swap (it re-exports type, Type, and ArkErrors = OmpErrors). 8 files moved over. One cosmetic delta: .expression renders number % 1 | undefined instead of <= 100. The bound is still enforced — probed 101 and 1000 rejected, 100/1/omitted accepted — so comms.test.ts now asserts enforcement through the existing rejects helper instead of matching a rendered string.
  2. irchub. The SDK renamed the COOP peer-channel tool. Compass's gate keeps its own name (isIrcEnabled, tools/hub/messaging.ts:109-110) with identical taskDepth > 0 semantics, so this is a rename in tests and citations only.
  3. Progressive tool disclosure — loadMode: "essential". 18.x defaults an omitted loadMode at every adapter boundary (including SDK custom tools) to "discoverable": registered, but not in the model's top-level callable schema. Left alone, the container agent would start with none of comms_* / agents_* / forge_* / board_* directly callable. Set once at the single registration seam (cli.ts nativeTools) rather than across 19 tool literals in 4 factories; the test helper mirrors it.
  4. Compaction supersession moved seam. elideSupersededCompactionEntries is gone; elision is no longer a load-time step. It now happens during context assembly (session/session-context.ts:377, on the display-transcript path), and stored entries keep their real summaries. The round-trip test now asserts the chain survives load intact, which is what it was actually there to prove.
  5. Project-prompt footer restructured. The cwd line moved out of project-prompt.md into a separate date-cwd-reminder.md block this render path does not include. The assertion now checks <workstation>, which is what the footer renders.

Verification

  • bunx tsc --noEmit — 0 errors
  • bun test677 pass / 0 fail across 28 files (up from 380 pre-migration: the real SDK runtime now loads, so previously-skipped suites execute)
  • moon run compass-agent:ci — 5/5 tasks pass
  • moon run toolchain-parity:ci — green, all 71 pinned tools match the dev shell

The nodeModules fixed-output derivation's outputHash is repinned (sha256-JbgM44Aw…). This is REQUIRED, not incidental: the FOD pins the installed dependency tree, so changing the closure (adding omptype, dropping arktype, bumping versions) while leaving the hash pinned makes the container build install the OLD 16.5.2 tree — which has no omptype, so the bundle failed with Could not resolve: "@oh-my-pi/omptype/ark". The local bun test cannot catch this (it reads the real node_modules); only the container build does. Verified by building the image: nix run … container build agent now produces image-compass-agent.json.

agent-image/entrypoint.nix natives path re-grounded against the installed package: --external omp-legacy-pi-modules still matches the SDK's optional dynamic import (legacy-pi-compat.ts:751), the loader's execDir candidate is confirmed at loader-state.js:779, and both .node CPU variants exist at the copy path. Upstream natives naming is self-consistent (pi-natives resolving pi-natives-linux-x64), so the addon-resolution bug the renamed fork build carries does not apply here.

Spec-impact: none. Refs RIG-2407

Co-authored-by: Matt Wilkinson matt@rigel.build

…oh-my-pi/* 18.0.11 (RIG-2407)

Repoints `compass-agent` from the vendored `forks/oh-my-pi/` subtree onto the published upstream SDK, `@oh-my-pi/{pi-agent-core,pi-ai,pi-coding-agent,omptype}@^18.0.11`. This is PR1 of 3: runtime only. The generator repoint (PR2) and the actual deletion of `forks/oh-my-pi/` (PR3) stack on top, so the subtree is still present and still the generator's schema source after this lands.

### Why upstream, not the Rigel fork

`forks/README.md:151-155` records the subtree's Sealed changes as **NONE** — byte-identical to upstream, with one sealed-added `forks/oh-my-pi/moon.yml`. The `forks/oh-my-pi/patches/` dir is upstream's own `patchedDependencies`, not ours. With no compass-specific patches to preserve there is nothing to carry, so this points at upstream `latest` and drops the vendoring rather than tracking a rebrand.

`bunfig.toml` is untouched: 18.0.11 published 2026-08-29 and is already past the 5-day `minimumReleaseAge` soak, so no exemption entry was needed.

### The five API deltas, each fixed on the real contract

1. **`arktype` → `@oh-my-pi/omptype/ark`.** The direct `arktype` dependency is dropped; `omptype` is an ArkType-compatible lazy-JIT implementation whose `/ark` facade is purpose-built for this swap (it re-exports `type`, `Type`, and `ArkErrors = OmpErrors`). 8 files moved over. One cosmetic delta: `.expression` renders `number % 1 | undefined` instead of `<= 100`. The bound is still **enforced** — probed 101 and 1000 rejected, 100/1/omitted accepted — so `comms.test.ts` now asserts enforcement through the existing `rejects` helper instead of matching a rendered string.
2. **`irc` → `hub`.** The SDK renamed the COOP peer-channel tool. Compass's gate keeps its own name (`isIrcEnabled`, `tools/hub/messaging.ts:109-110`) with identical `taskDepth > 0` semantics, so this is a rename in tests and citations only.
3. **Progressive tool disclosure — `loadMode: "essential"`.** 18.x defaults an omitted `loadMode` at every adapter boundary (including SDK custom tools) to `"discoverable"`: registered, but not in the model's top-level callable schema. Left alone, the container agent would start with none of `comms_*` / `agents_*` / `forge_*` / `board_*` directly callable. Set once at the single registration seam (`cli.ts` `nativeTools`) rather than across 19 tool literals in 4 factories; the test helper mirrors it.
4. **Compaction supersession moved seam.** `elideSupersededCompactionEntries` is gone; elision is no longer a load-time step. It now happens during context assembly (`session/session-context.ts:377`, on the display-transcript path), and stored entries keep their real summaries. The round-trip test now asserts the chain survives load intact, which is what it was actually there to prove.
5. **Project-prompt footer restructured.** The cwd line moved out of `project-prompt.md` into a separate `date-cwd-reminder.md` block this render path does not include. The assertion now checks `<workstation>`, which is what the footer renders.

### Verification

- `bunx tsc --noEmit` — 0 errors
- `bun test` — **677 pass / 0 fail** across 28 files (up from 380 pre-migration: the real SDK runtime now loads, so previously-skipped suites execute)
- `moon run compass-agent:ci` — 5/5 tasks pass
- `moon run toolchain-parity:ci` — green, all 71 pinned tools match the dev shell

The `nodeModules` fixed-output derivation's `outputHash` is repinned (`sha256-JbgM44Aw…`). This is REQUIRED, not incidental: the FOD pins the installed dependency tree, so changing the closure (adding `omptype`, dropping `arktype`, bumping versions) while leaving the hash pinned makes the container build install the OLD 16.5.2 tree — which has no `omptype`, so the bundle failed with `Could not resolve: "@oh-my-pi/omptype/ark"`. The local `bun test` cannot catch this (it reads the real `node_modules`); only the container build does. Verified by building the image: `nix run … container build agent` now produces `image-compass-agent.json`.

`agent-image/entrypoint.nix` natives path re-grounded against the installed package: `--external omp-legacy-pi-modules` still matches the SDK's optional dynamic import (`legacy-pi-compat.ts:751`), the loader's `execDir` candidate is confirmed at `loader-state.js:779`, and both `.node` CPU variants exist at the copy path. Upstream natives naming is self-consistent (`pi-natives` resolving `pi-natives-linux-x64`), so the addon-resolution bug the renamed fork build carries does not apply here.

Spec-impact: none. Refs RIG-2407

Co-authored-by: Matt Wilkinson <matt@rigel.build>
@linear-code

linear-code Bot commented Sep 6, 2026

Copy link
Copy Markdown

RIG-2407

@trunk-io

trunk-io Bot commented Sep 6, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown

Compass engineering docs preview: https://compass-repo-rig-2407-sdk-mi.compass-eng-docs.pages.dev

Deployed from compass-repo/rig-2407-sdk-migration at 534e659.

rigel-mintaka and others added 2 commits September 6, 2026 00:44
…adMode on all custom tools (RIG-2407)

Addresses the review of #924. Two `high` findings, both real defects the migration's own verification missed, plus the mediums and lows.

### high — a `.narrow` rule could reach the model through no channel at all

Under omptype a `.describe()` applied after a `.narrow()` SHADOWS the narrow's `ctx.mustBe(...)` reason in the rejection message; arktype appended it. Since a `.narrow` predicate also has no JSON Schema form, a rule spelled only in the narrow became unreachable: the model was told the field's PURPOSE and never the violated RULE, so it could not self-correct and would retry the same invalid value.

Verified in isolation (identical schema, same call):

```
withDescribe => must be A label (was "toolongvalue")
noDescribe   => must be at most 5 chars (was "toolongvalue")
```

Fixed by folding every narrow's rule into its description. The two shared `nonBlank` helpers (`forge.ts`, `board.ts`) now append `(must not be blank)` themselves, so no call site can forget it; the hand-written descriptions in `comms.ts` (`topic` ×3, `channel` ×2, both `peer_handle`s) state their non-blank and ≤120-character bounds explicitly. Audited all 22 `mustBe` sites across the four files; every rule now appears in its description.

The blind spot was the assertion, not just the schema: the migration rewrote `comms.test.ts` to check `rejects(...)` booleans, and the boolean verdicts genuinely did not change. Added a regression test that pins the MESSAGE — the surface the harness feeds back to the model verbatim (`pi-ai/src/utils/validation.ts:1722`). Confirmed red-green: reverting one description to its pre-fix text fails the test, restoring it passes.

### high — the loadMode stamp missed every mounted-MCP tool

The stamp was applied to `nativeTools` only, but MCP tools merge into the SAME `customTools` array and go through the same adapter. `MCPManager.getTools()` never sets `loadMode` (0 matches under `src/mcp/`), so every mounted-MCP tool silently defaulted to `"discoverable"` — registered but absent from the model's top-level callable schema. The `xd://` transport does not recover them in this session shape: it is gated on a top-level `write` tool this headless session never requests (`tools/index.ts:772`). Net effect was a tool neither top-level callable nor `xd://`-reachable, which is exactly the silent-no-surface failure the RIG-1741/CD-3 mount contract exists to prevent.

Moved the stamp to the merged array at the real registration seam, so natives and MCP tools get one uniform documented mode. The mount test now asserts PRESENTATION (`loadMode: "essential"` per tool) rather than bare array membership — membership passed while the tool was unreachable.

### medium / low

- Dropped the hardcoded `18.0.3` from the omptype pin note in `comms.ts` (every artifact in the tree says 18.0.11); the lockstep rule is the durable content and `package.json` is the authority for the number.
- Recorded the second omptype introspection delta in the `comms.ts` header: `.get(k).description` on an optional UNION node returns the rendered union rather than the authored text, so description contracts must be asserted through `arkToWireSchema`, never `.get().description`.
- Fixed the `cli.test.ts` in-body comment that still claimed `elideSupersededCompactionEntries` collapses a summary on load — it contradicted the rewritten assertion 30 lines below it.
- Finished the `irc` → `hub` rename in `subagent-tool-split.test.ts`'s docblock (the file's own documentation of what it defends), corrected `lockfile 16.5.2` → `18.0.11`, and repointed the stale citations: `executor.ts:2394/:2489` → `src/task/executor.ts:3113/:3234`, `cli.ts:896` → `cli.ts:926`.
- Sorted `@oh-my-pi/omptype` into alphabetical order in `package.json`.

### Verification

- `bunx tsc --noEmit` — 0 errors
- `bun test` — **678 pass / 0 fail** across 28 files (677 + the new message-level regression test)
- `biome check packages/compass-agent` — 0 errors

Spec-impact: none. Refs RIG-2407

Co-authored-by: Matt Wilkinson <matt@rigel.build>
…de (RIG-2407)

Addresses round 2 of the review of #924. The previous fix commit introduced a high-severity regression; this corrects it and adds the coverage that should have caught it.

### high — the loadMode stamp destroyed every mounted-MCP tool's `execute`

The stamp was written as `.map((tool) => ({ ...tool, loadMode: "essential" }))`. `mcp.tools` are `MCPTool` CLASS instances (`pi-coding-agent src/mcp/tool-bridge.ts:492`, built by `fromTools` at `:514`) whose `execute` / `renderCall` / `renderResult` live on the **prototype**. An object spread copies only own enumerable properties, so all three were silently sheared off, and the SDK adapter's `tool.execute(...)` (`sdk.ts:989`) would throw `is not a function` at the model's first call.

That traded a demoted-but-intact tool for a top-level-callable one that crashes — strictly worse than the bug the previous commit fixed. The natives are plain object literals, so they were unaffected, which is why nothing reddened.

Reproduced directly:

```
instance execute: function | renderCall: function
SPREAD   execute: undefined | renderCall: undefined
FIXED    execute: function | renderCall: function | loadMode: essential
```

Fixed by cloning onto the original prototype (`Object.assign(Object.create(Object.getPrototypeOf(tool)), tool, { loadMode })`), which applies the mode without reconstructing the object or mutating the SDK's own instances.

### medium — the fixture could not see it

The mount test's fake was `[{ name: "db.query" }]`, a prototype-less literal. Spreading a literal is lossless, so the assertion passed identically whether the implementation spread or not: the test validated the stamp while being blind to the damage the stamp did. Replaced with a class-based fake and added assertions that `execute` and `renderCall` survive. Confirmed red-green — reverting to the spread fails it, the corrected stamp passes.

### medium — the shared `nonBlank` helpers stuttered the rule

The previous commit appended `(must not be blank)` unconditionally, but 17 of 18 call sites already ended their text with `must not be blank`, so the model-facing wire schema read `"... must not be blank (must not be blank)"`. The append is now gated on whether the caller already states the rule; the one site that genuinely needed it (`body: nonBlank(STAMP_DESC)`) still gets it. Probed across both helper modules: 0 duplicates, 16 fields each stating the rule exactly once.

### low — helper-path message coverage, and prose

Round 1's regression test covered only the hand-written `comms.ts` descriptions, which is exactly how the stutter shipped past it green. Added a sibling test over the shared-helper path in `forge.test.ts` that pins both the rejection message and the absence of stutter in the model-facing schema (`arkToWireSchema`), also confirmed red-green. Re-wrapped a paragraph in `subagent-tool-split.test.ts` left broken by the previous commit's edit.

### Verification

- `bunx tsc --noEmit` — 0 errors
- `bun test` — **679 pass / 0 fail** across 28 files
- `biome check packages/compass-agent` — 0 errors
- All three new tests confirmed red-green against their specific defect

Spec-impact: none. Refs RIG-2407

Co-authored-by: Matt Wilkinson <matt@rigel.build>
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