AGENTS.md@apps/lemon_omp · git:20261015.48afb1f · 2026-10-15 · sha256 b8964691224d3898

AGENTS.md@apps/lemon_omp git:20261015.48afb1fA

Immutable. This exact content never changes and is served at /api/v1/blob/b8964691224d3898.

# LemonOmp contributor guide

`lemon_omp` drives omp (oh-my-pi) as an external delegate. It is not an
executor or engine: Lemon's own runs stay native. See [README](README.md) and
the [operator guide](../../docs/user-guide/omp.md).

## Boundaries

- Dependencies are `lemon_core`, `lemon_agent`, `lemon_ai`. The `omp` tool
  registers through `LemonAgent.ToolRegistry`; `coding_agent` must not name this
  app. The control plane depends on it directly for `omp.*` methods; CLI and
  TUI reach it only through the control plane.
- Speak omp's documented RPC protocol (v1 JSONL over stdio). Do not scrape
  terminal output or parse omp's interactive UI. Session files are read-only:
  never write, lock or migrate omp state.

## Process lifecycle

- One `LemonOmp.Session` owns one OS process per invocation, `restart:
  :temporary`. Never relaunch automatically; a relaunch could replay a prompt
  omp already acted on. Resume is an explicit new invocation with a new id.
- omp exits on stdin EOF. Keep the port as the owner of omp's stdin so any
  death of the session process ends omp; `terminate/2` escalates TERM/KILL
  (descendants included) after a grace period.
- Strip `LEMON_*` variables from omp's environment. Reject caller-supplied
  paths other than the validated `cwd`; resume accepts session ids, not paths.
- New history belongs in Lemon's configured private session directory, created
  `0700` before spawn. Explicit omp session directories are flat, not cwd buckets.
  Discovery reads that directory and omp's default buckets without mutation.
  Resume derives its directory from the matched discovery entry, never from
  caller paths or arbitrary header paths, and continues that source in place.

## Control and trust

- Acknowledge steer/follow-up/cancel only after omp's correlated success
  response. omp errors are rejections; silence is `:outcome_unknown` and must
  not be retried automatically. Closed/crashed invocations fail closed.
- Agent callers are scoped to their own session key; a foreign id reads as not
  found. Operator surfaces use `owner: :any`.
- Everything from omp is untrusted. Bound and sanitize it in `Projection`, mark
  tool results untrusted, and bridge tool approval selects through
  `LemonCore.ExecApprovals` without blocking the Session. Cancel pending approvals
  when omp abandons a dialog or the session ends; decline all other blocking UI
  requests. Reject host-tool/host-URI requests; Lemon registers none.

## Verification

From the umbrella root: `scripts/test path apps/lemon_omp/test`. By default,
tests use the fake omp script in `test/support` and exclude `:omp_live`.
They restore application env and confirm OS processes are gone.

Opt in to the real-binary smoke (lifecycle, approvals bridge, parallel
workers) with:

```bash
LEMON_OMP_LIVE=1 scripts/test path apps/lemon_omp/test/lemon_omp/live_smoke_test.exs
```

This reads the developer's real `~/.omp/agent` credentials despite the test HOME
sandbox; `LEMON_OMP_LIVE_AGENT_DIR` overrides that credential/settings source.
Every test pins `:session_dir` to a throwaway directory, so Lemon-run transcripts
never go into the developer's omp history. Choose the binary with `LEMON_OMP_BIN`.
`LEMON_OMP_LIVE_MODEL` defaults to
`openrouter/nvidia/nemotron-3-super-120b-a12b:free`; only `:free` models are
accepted. Workspace edits also stay in throwaway git workspaces.