Skip to content

feat(agents): oh-my-pi (omp) as a built-in agent - #360

Merged
simion merged 1 commit into
simion:mainfrom
lymanzhao:feat/omp-agent
Oct 5, 2026
Merged

simion merged 1 commit into
simion:mainfrom
lymanzhao:feat/omp-agent

Conversation

@lymanzhao

Copy link
Copy Markdown
Contributor

oh-my-pi (omp) is a terminal-first coding agent, rewritten from pi (badlogic's pi-mono), which termic already supports. This adds it as the eleventh built-in, measured against a live 18.6.0 per docs/adding-an-agent.md rather than written from --help: every default below carries the version and the observation in a comment.

What omp measured as, and what shipped

Resume is capture-shaped. -r <uuid> resumes a session with its history, -r <unknown> fails fast with exit 1 ("Session not found"), -r with no value opens omp's own picker, and nothing accepts an id at launch. So: resume_id_args ["--resume", "{UUID}"], resume_picker_args ["--resume"] (all three picker facts measured: it opens, picking reports the id through the plugin, Esc leaves at exit 0 so it cannot loop), and no mint.

Approvals default to yolo, so yolo_args is empty on purpose. omp's own tools.approvalMode defaults to "yolo": a file write inside AND outside the workspace executed unasked, in both the TUI and -p. Same reasoning as pi's empty entry, recorded so it does not read as a skipped measurement.

It bounces out of $HOME. A spawn in the home directory silently moved to a temp dir (the session recorded cwd /tmp), so unattended spawns compose --allow-home, run-scoped.

Hooks ride the pi plugin transport. omp kept pi's layout: it autoloads ~/.omp/agent/extensions/termic.ts, transpiling in-process, with explicit legacy-pi compat. The extension is adapted to what actually moved:

  • Done is agent_end, suppressed while the event's willContinue flag is set (omp's own docs: such an agent_end is not a terminal settle).
  • Attention is tool_approval_requested (wrapper.ts fires it only when a tool genuinely needs a prompt, so yolo runs never false-attention) plus tool_execution_start on ask, the same edge omp's Warp bridge maps to question_asked. Answering clears it exactly, not on the next heartbeat.
  • Subagents run in-process under their own id and are dropped via ctx.agent.kind (the muse trap).
  • The session id is reported on session_start / session_switch / agent_end: omp mints its own UUIDv7 at startup and the plugin's report is the resume binding. session_start fires at startup, so there is no capture backstop (omp's only lister is the interactive picker).
  • omp's own OSC 9;omp: Complete per turn is anchored in BUILTIN_NOTIFY_IGNORE; its Stopped with error body is deliberately NOT ignored, because a died turn should ring.

Usage is pulled cold. omp usage --json prints structured per-provider limits (window id, durationMs, resetsAt, usedFraction). agent_usage_omp spawns it pointed at the entry's PI_CONFIG_DIR, the same account resolution codex's command does, so omp gets the plan-usage chip and reports_usage (the pinned test is updated by name).

Title signals are captured, not guessed. Live pty capture: π ⠙ <dir> while working (braille frame after the brand mark), π > <dir> at the composer. Attention stays empty, honestly: default approvals never block, so no blocked title exists to pattern.

Login store: ConfigDir(PI_CONFIG_DIR). Measured both ways: an empty relocated root prints "No credentials found" while the real environment lists two signed-in accounts, and the credential is a SQLite row in the agent dir's agent.db, not a keyring item. One caveat written down: omp's PI_CODING_AGENT_DIR outranks PI_CONFIG_DIR when a user sets both. make login-probe AGENT=omp passes.

Docker: KNOWN_SAFE_AGENTS + BASE_BUILTINS + a Dockerfile line. The omp.sh installer drops the binary in $HOME/.local/bin (outside the mounted ~/.omp), so the mount cannot shadow it — not grok's problem. Relocation is the claude shape: the var names the whole root, which is what Docker mounts.

Rejected, with the record: omp ships a second channel, WARP_CLI_AGENT_PROTOCOL_VERSION=1 → structured OSC 777 frames with stop / permission_request / question_asked. Zero-install detection was tempting, but it would mean a bespoke Rust parser and a second event contract beside a plugin transport that already covers the same states. docs/agent-hooks.md carries the reasoning so nobody re-investigates.

Compatibility with the Cursor CLI commit

Rebased over 905152e (cursor as a built-in). Two enumerations cursor's commit left stale are brought current here, since omp's change touches them anyway: the README built-in list and the reset-all confirmation copy in both locales now name every built-in including cursor. No cursor behavior is otherwise touched.

Verification

  • cargo test: 1241 passing (includes the omp seeded-default test, the plugin-driven node test, and the guards that derive from the registry).
  • npm test: 2751 passing (spawn composition for omp, footer sources, fallback agreement).
  • make login-probe AGENT=omp: ok, no drift.
  • Full make e2e on the rebased branch: 24/25 specs; git.e2e.ts fails for a local-path reason (the checkout path contains "github", so forge.rs's substring fallback resolves the fixture remote as github and reports cli-unauthed) — reproducible on main without this change.
  • With agent hooks flipping on by default (a57a2d3), omp's plugin will auto-install at the next launch for everyone; the install is a whole-file write plus manifest with no prompts and no config merge, which is the unattended-safe shape the runbook requires.

Manual verification

The maintainer ran this change by hand in the app before it was opened. Everything above the human pass came from live probes against omp 18.6.0 (startup, resume, approval modes, titles, extension events, usage JSON), captured and quoted where a claim is made.

🤖 Generated with Claude Code

omp (github.com/can1357/oh-my-pi) is pi's rewritten fork. Everything
shipped here was measured against a live 18.6.0 per
docs/adding-an-agent.md, with the version and observation in the
comments:

- Registry: capture-shaped resume (`-r <uuid>` with history, `-r` alone
  opens omp's own picker, no mint flag), empty yolo_args because omp's
  own tools.approvalMode defaults to yolo (a write inside AND outside
  the workspace executed unasked), title signals captured off a live
  pty (pi mark + spinner frame, `pi >` at the composer),
  UNATTENDED_SPAWN_ARGS --allow-home because omp silently moves off
  $HOME to a temp dir otherwise.
- Hooks: the pi plugin transport carries over (omp autoloads
  ~/.omp/agent/extensions/*.ts with pi-compatible machinery), with
  omp's event differences: done on agent_end guarded by willContinue,
  attention from tool_approval_requested plus the ask edge, subagent
  sessions dropped via ctx.agent.kind, and the session id REPORTED on
  session_start (fires at startup), which is the resume binding. A
  node-driven test runs the real generated extension against the
  measured event shapes.
- Login store: ConfigDir over PI_CONFIG_DIR, measured (the credential
  is a SQLite row in agent.db, no keyring); probe row added.
- Usage: agent_usage_omp spawns `omp usage --json` (measured
  structured limits), so omp reports_usage and gets the pull chip.
- Docker: KNOWN_SAFE_AGENTS + BASE_BUILTINS + a Dockerfile line (the
  omp.sh installer drops the binary in ~/.local/bin, outside the
  mounted ~/.omp, so no grok shadowing).
- Icon (classic serif pi, distinct from pi's filled staircase), brand
  color both themes, i18n reset lists brought current, README list.
- Docs: agent-hooks.md (per-agent rows, the Warp-protocol rejection,
  the omp: Complete notify anchor), agent-accounts.md, sandbox.md.

Suites: cargo test 1189 green, npm test 2618 green, make login-probe
AGENT=omp ok, full make e2e 24/25 specs (git.e2e.ts is the known
machine-specific failure: this checkout's path contains "github" and
gh is unauthenticated for it).

Co-Authored-By: Claude Code <noreply@anthropic.com>

@simion simion left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the security-relevant defaults for the omp integration: yolo_args is empty and matches pi's pattern (omp's own yolo approval mode is an upstream omp default, not something termic grants), --allow-home is run-scoped (only composed when unattended, never persisted), binary/plugin paths are static literals with no task-input interpolation, and the Seatbelt sandbox entry goes through the same sbpl_escape path as every other agent. Integration matches pi/opencode point-for-point, divergences are stated and test-pinned. No blocking issues.

@simion
simion merged commit ba53667 into simion:main Oct 5, 2026
7 checks passed
@simion

simion commented Oct 6, 2026

Copy link
Copy Markdown
Owner

Live in v1.13.4, released today.
Thanks.

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.

2 participants