omp packages

pi-omp-advisor0.7.3

A live advisor for pi and Oh My Pi that watches your session as it works and sends advice mid-run.

GitHub Actions Published 4 days ago MIT ~2.1K downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-omp-advisor

A live advisor that watches pi or Oh My Pi (OMP) as it works and can send it advice mid-run.

Not affiliated with, endorsed by, or supported by oh-my-pi / Stencil Labs, Inc., or by the pi project / Earendil Works. The name states what the code is a port of; neither upstream project has any involvement in it. "oh-my-pi", "omp", and "pi" are used nominatively to identify those projects, not as marks of this one. Also distinct from the unrelated pi-advisor npm package.

This is a port of the advisor/watchdog system in oh-my-pi onto pi's own Agent SDK — retaining its compact observer context, advise description, delivery channels, and emission guard. Small prompt clarifications and editable pending advice are specific to this port; PROVENANCE.md documents those differences.

What it does

On session start, pi-omp-advisor builds one live in-process AgentSession per configured advisor, each with its own model and its own throwaway context, and feeds it a compact skim of the primary session, much like someone glancing over your shoulder: normally one batch per primary turn, with your messages and the primary's replies in full and one short card per tool call. Full tool-result bodies and read-file contents stay out; Pi's own short collapsed result preview, first-line error previews and bounded edit/write diffs may be included. Its recent model context is bounded to 48,000 estimated input tokens by default, and primary-agent reasoning is excluded unless explicitly enabled.

An advisor reaches the primary agent and operator through advise(note, severity?, ShortTitle?) and update_advice(targetId, note, ShortTitle?, severity?). Alongside them, an advisor gets whatever investigative tools its config grants — by default the read-only set read, grep, find (configured as glob too) — so it can check a claim before raising it. Advice-management tool responses include a live snapshot of its pending queue and review allowance, avoiding separate state-query turns. How a note reaches the primary depends on severity and on what the primary is doing:

Situation Channel
nit during active work aside — batched, delivered at the next step boundary, no interruption
concern / blocker steer — interrupts the live turn, or triggers one when idle
Primary already gave its final answer, nothing queued preserve — visible, cancellable inbox entry; released when the primary next starts working (blocker still steers)
Within immuneTurns (default 3) of a previous interrupt concerns downgraded to aside (blocker exempt)
Print mode while idle, or a stopped/aborting primary run preserve — including blockers after a stop

Escape aborting a primary run leaves auto-resume suppressed after the live abort signal disappears. Restarting an advisor does not clear that stop; submitting a new normal user prompt does. A blocker can still restart a naturally completed run that you did not stop.

Non-blocker notes raised during work stay deferred. A completing update gives the advisor a chance to reconsider them before they enter these delivery channels; a failed or aborted review does not release them. Blockers retain immediate routing. This is review between completed model/tool cycles, not a pre-execution check of each tool call.

Two gates keep the primary's transcript clean even when an advisor model misbehaves: a noise filter (stop, done, lgtm, no issues, …) and an allowance of up to 3 accepted notes per review update (with allowance metadata [Review allowance: N of 3 used] returned on every tool completion), both applied at the tool-call boundary before a note can enter the delivery state machine.

Preserved notes remain in an extension-owned Advisor inbox instead of pi's invisible nextTurn queue. A widget above the editor shows up to three queued notes immediately. Open the inbox with Ctrl+Shift+A or /advisor inbox to deliver or dismiss one note, or deliver/dismiss the whole queue. Ctrl+Shift+R pauses or resumes observation without releasing the queue; Ctrl+Shift+X clears the queue immediately. The inbox only holds notes while the primary is at rest. When it starts working again, the queue is released into that run, whatever started it: a normal prompt (cards render above your message and join that turn's context), or a Parley message, background-task or subagent notification, or any other wake (cards are steered into the run before its first response). Resuming the advisor or turning it on mid-run releases the queue into the active run the same way. Queue and pause state are persisted as session metadata, so they survive extension reloads. Late ordinary notes, including default-severity nits, stay here for you to read and discard before the primary next starts working. Typing alone does not release them. Asides queued during work are checked again at handoff in case the primary has since finished or been stopped. On pi, one more release gesture exists for the empty-chat case: with the session idle, the editor empty, and something queued, a bare Enter delivers the whole queue and starts the turn (pi drops empty submits before extensions' input event, so this hooks the editor's own input path). An Enter claimed by a dialog, picker, or autocomplete never reaches it, and it never fires while the advisor is paused or the agent is mid-run.

OMP 18.2.4 does not expose pi's composable editor API, so it cannot support that optional empty-Enter gesture. A preserved OMP note remains queued until the primary next starts working, or until you explicitly deliver it from /pi-advisor inbox. The inbox widget/card is not a receipt that the note already reached the primary agent; there is no automatic standalone delivery in this case.

The advisor can inspect its own unsent notes with pending_advice, replace their text with revise_advice, or remove them with withdraw_advice. A small pending summary accompanies its updates. These tools add no repository access and cannot change another advisor's notes or recall a message already handed to Pi. Revisions keep the same ID and severity and update the visible inbox when applicable. User dismissal wins: a withdrawn or cleared note cannot be resurrected by revision. Successful revisions also update duplicate tracking without spending a new-note slot, so their replacement text is not accepted again as fresh advice.

Pending IDs survive within-session model-context rebuilds; preserved inbox notes also survive reloads. Deferred notes still being reviewed are in-memory only. A new run releases the preserved inbox immediately, so the advisor does not get a fresh review of that run's prompt before those notes are handed off.

A limited memory, not a second full transcript

Each advisor has a bounded context budget. Its prefix grows unchanged for prompt-cache reuse until a new review would cross the ceiling; then all history before that review expires at once. Nothing summarizes or replays the reasoning path that filled the old context. Pending advice is stored separately, so a reset does not remove its IDs or prevent revision/withdrawal.

Keeping the bigger picture (optional notebook)

Off by default. When a context reset drops the person's own words, an advisor can drift toward whatever the agents are currently absorbed in, and in long multi-AI runs asides can harden into invented rules. With notebook: true (toggle it under /advisor → settings → Advisor ON/OFF, saved to that scope's WATCHDOG.yml), each advisor gets a notebook tool for one short note (up to 1,200 characters) about what the person is trying to accomplish.

The note is shown again only when a context window starts with no earlier history: a fresh or rebuilt advisor, or a reset before, during, or after a review. It is framed as the advisor's own earlier interpretation, not something the person said, and its size counts toward the context budget. Notes are saved as session entries, so they follow the branch they were written on and survive reloads. A short system-prompt section explains the purpose; there are no rules or approval steps. Ask the advisor in chat if you want to see or discuss its note.

Other extensions can show notes read-only. Each write appends a session entry (customType: "pi-omp-advisor-notebook", data { advisor?, text, updatedAt }; advisor is absent for the single default advisor). The latest entry per advisor on the active branch is its current note. After each write, pi.events also emits pi-omp-advisor:notebook:v1 with the same data, as a hint to re-read the branch.

What the feed shows, and usage

Each tool call is one card: the command, path, or main argument (up to 120 characters), its status and size, the first line of an error, a bounded diff for edits and writes, and Pi's own short collapsed preview of a successful result, the lines you see under the call before expanding it. Built-in tools use Pi's built-in renderers, as Pi does for any tool with a built-in name that brings no renderer of its own: a shell shows its last few lines, and a read shows no file body. Other tools get Pi's generic preview (their first text lines), capped at about 600 characters (roughly six lines), since their own renderers aren't reachable from another extension. OMP and non-interactive modes show no preview. On one real, shell-heavy session this added about a third more input. When calls run in parallel, each card updates in place as its result arrives. If conversation arrives before a result, such as your correction or a peer's message, the completion is shown where it arrived. Extension messages, advisor notes, and summaries appear as labeled previews of up to 500 characters; messages hidden from your screen are hidden from the advisor too. Labels follow Pi rather than knowing particular extensions: an extension message is labeled with its message type, as Pi's default view labels it, and each tool card names who provides the tool (built-in or the registering extension/package). A User message is Pi's user-role message; extensions can send those too, so it isn't proof a person typed it.

/advisor status shows cumulative reported usage since the advisor runtime started: uncached input, cache-read/write, output (including reported reasoning), model attempts/responses, and tool requests. Counts survive model-context rebuilds and history expiry; a new runtime or reload starts fresh. SDK/provider dollar figures are estimates, not invoices. Missing usage or zero/missing pricing is shown as unavailable or partial, not free. Provider-internal retries can remain unreported. These counters don't add model requests, enter its prompt, or enforce a spending stop.

The context limit applies before every model request, including follow-ups after the advisor's own tool calls. It counts estimated system/tool overhead as well as conversation text, and is reduced on small models to leave reply headroom. Estimates use Pi's character-based heuristic, not an exact provider tokenizer. A budget is a ceiling, not a target amount of history to fill. It limits future model input, not host session records or the provider's data-retention policy.

The latest observation and current tool exchange are kept. Large observation text or tool output can be shortened with an explicit omission marker; actual assistant tool calls and their result identities are not rewritten. If a required exchange cannot fit safely, that review fails without releasing deferred advice. An incomplete exchange from a known interrupted review is discarded as a whole, without inventing missing results or treating the review as successful. Standing system/project instructions are not silently cut to make room.

Use /advisor config to change each advisor's budget and primary-reasoning setting. /advisor status shows each advisor's effective provider/model, estimated budget, retained message count, whole-history reset count, advisor wakes/model requests/tool calls, and pending turn batch. The live route also appears in the compact footer, stream selector/header, inbox labels, and delivered Advisor cards.

When an advisor fails (a review errors, its session cannot start, or its model: does not resolve), the footer shows ⚠ next to it, for example advisor: gpt-6-astra ON ⚠, and /advisor status shows the error and when it happened. A failing advisor stays on and retries at its next review; the mark clears when a review succeeds, and status keeps the last error, marked recovered. In the TUI, these errors are not written to the terminal, where they would land on top of the editor. Print, JSON, and RPC runs still write them to stderr. Cards record the model that generated each advise tool call; an in-place revision updates that provenance to the model that generated the replacement text. Changing this extension still requires /reload before the new policy is active. On OMP, use the namespaced /pi-advisor command because /advisor is reserved by OMP's native advisor.

Optional emergency stop

Add request_stop explicitly to an advisor's tools to grant cancellation:

advisors:
  - name: advisor
    tools: [read, grep, glob, request_stop]

That advisor also gets current_tool, which returns the exact execution targetId and a compact call summary. Tool lifecycle events update this host-side state but never wake the advisor model. A normally batched review samples the live state immediately before prompting. Separately, the oldest-message timeout can review a finalized assistant tool-call message while its tool is still running; the default is intentionally four minutes, including when maxBehind is 1. This makes cancellation a rare fallback rather than one model request per tool start.

request_stop(targetId, reason) uses Pi's supported active-turn abort, not arbitrary process control. It accepts only a sole in-flight foreground call and rejects stale or ambiguous targets, multiple calls, paused/off advisors, and repeated requests until the run settles. The target ID is unique even when a provider reuses its own tool-call ID. A blocker advisory alone does not invoke cancellation.

The reason is shown immediately and recorded in session metadata. After the primary settles, a visible receipt reports what was observed without restarting it. Acceptance means cancellation requested, not guaranteed termination or rollback: tools must cooperate with abort, and detached jobs or already-completed external side effects are not undone. Resume explicitly after inspecting the reason. After changing the grant or extension code, run /reload before testing.

Install

Running from an editable checkout (recommended if you intend to modify it — the checkout is the install, so edits are live and updates are git pull):

curl -fsSL https://raw.githubusercontent.com/Scott-Meyer/pi-omp-advisor/main/scripts/bootstrap-machine.sh | bash

That clones to $HOME/git/pi-omp-advisor, installs dependencies, and registers it as a local-path package. Because pi resolves that path relative to ~/.pi/agent, the same settings entry works on every machine.

Or install it as a managed pi package, so its dependencies travel with it:

pi install npm:pi-omp-advisor
pi install git:github.com/Scott-Meyer/pi-omp-advisor@v0.5.2

Prereleases publish to the next channel. pi install npm:pi-omp-advisor@next switches an existing npm install to it (pi replaces the entry for the same package), and pi update then follows that channel. pi install npm:pi-omp-advisor@latest returns to stable immediately, even from a newer prerelease, and then follows stable releases.

Requires pi 0.84.2 or newer (it uses createAgentSession, DefaultResourceLoader, and loadProjectContextFiles; on an older pi a missing export throws during extension load, which takes down the whole session) and Node 22.19+, matching pi itself.

or add the checkout to packages in ~/.pi/agent/settings.json (paths are resolved relative to the agent dir):

{ "packages": ["../../git/pi-omp-advisor"] }

That is enough: normal sessions immediately run one implicit default advisor on the active chat model, including a model selected with --model. No WATCHDOG.yml or command is required. This means installation adds a second model call after ordinary primary turns; use /advisor off for the current session or persist main: false when that is not wanted.

WATCHDOG.yml is optional customization for model choice, cadence, tools, and named advisor rosters. A WATCHDOG.md by itself adds standing instructions to the implicit default advisor.

Do not symlink src/ into ~/.pi/agent/extensions/. That directory is for single-file/self-contained extensions and gets no dependency installation. pi-omp-advisor needs the yaml package, which pi does not provide to extensions (pi's host-provided set is @earendil-works/pi-ai, pi-agent-core, pi-coding-agent, pi-tui, and typebox). A symlinked src/ resolves yaml only by accident — either because the symlink target happens to sit next to a node_modules/ containing it, or because some unrelated package hoisted it into ~/.pi/agent/npm/node_modules. Move the code to a machine where neither holds and every pi session fails to start. Installing as a package makes pi run npm install for it instead.

A missing yaml no longer throws at load: WATCHDOG.yml/.yaml are skipped with an actionable error and WATCHDOG.md still works, so a packaging mistake degrades pi-omp-advisor rather than bricking pi.

Configuration

WATCHDOG.yml (or .yaml), discovered at user scope (~/.pi/agent/WATCHDOG.yml), at the project root, and walking up from cwd — including inside a .omp/ directory. A more specific file replaces an advisor with the same name.

main: true         # run in normal pi sessions (default true)
subagents: false   # run inside subagent processes too (default false; see note below)
syncBacklog: off   # backpressure: off, queued turns, or { pauseAt: 4, resumeAt: 1 } hysteresis
maxBehind: 3       # completed primary turns accumulated per advisor wake (default 3, min 1)
flushTimeoutMs: 240000 # maximum age of the oldest unseen message (default 4 minutes, min 100ms)
flushOnSettled: true # deliver pending observations as soon as the primary finishes (default true; set false for strict turn batching)
notebook: false    # optional advisor notebook (default off; see "Keeping the bigger picture")

advisors:
  - name: advisor
    model: openai/gpt-5.1-codex-mini   # or provider/id:high for a thinking level
    tools: [read, grep, glob]     # default; `glob` maps to pi's `find`
    contextTokens: 48000         # default estimated input ceiling; configurable, minimum 2048
    includePrimaryThinking: false  # default; independent of the advisor's own thinking level
    maxBehind: 3          # per-advisor turn-batch override
    flushTimeoutMs: 240000 # per-advisor maximum wait for a partial batch
    flushOnSettled: true  # per-advisor settlement-flush override
    instructions: Pay extra attention to auth and data-loss risk.
    enabled: true

WATCHDOG.md files on the same search path are loaded as freeform standing instructions shared by every advisor.

Set model explicitly to choose a different advisor model. If omitted, the advisor inherits the active chat model and thinking level, including its live provider/authentication route. Pick something fast when overriding it — an advisor's judgment always lags the primary by its own round-trip time.

Commands

  • /advisor on | off — enable/disable for this process (on restarts the implicit default advisor when no roster is configured)
  • /advisor main on|off — default for normal sessions
  • /advisor subagents on|off — default inside subagent processes (PI_ADVISOR_SUBAGENTS=1|0 overrides per process)

How subagent detection works. pi core does not mark subagent processes. Both subagents: and PI_ADVISOR_SUBAGENTS apply only when the spawner sets PI_SUBAGENT_CHILD=1 on the child — which the pi-subagents package does. A custom spawner must therefore set PI_SUBAGENT_CHILD=1 to be recognized at all, and may additionally set PI_ADVISOR_SUBAGENTS=1|0 to override subagents: for that child. Without PI_SUBAGENT_CHILD=1 a child is treated as an ordinary main session and follows main: — setting PI_ADVISOR_SUBAGENTS alone does nothing.

  • /advisor — open the control menu. ON/OFF now (this session only) is temporary; to save ON/OFF, open Settings for this project or User defaults for all projects, then choose the first item, Advisor ON/OFF. The saved main-session and subagent defaults are separate; project settings override user defaults. Subcommands also provide descriptions and Tab completion
  • /advisor status — which advisors are running, and their state
  • /advisor inbox — inspect, deliver, or dismiss preserved advisories waiting for the primary to start working again (Ctrl+Shift+A opens the same inbox)
  • /advisor stream [name] — open a read-only popup over one advisor's own chat stream: its observations, reviews, and tool use as it works, updating live. Deliberately rare-use — it exists for the cases where you really want to see what the advisor is seeing, not as part of the normal loop
  • /advisor pause / /advisor resume — suspend or resume observation while retaining the queue (Ctrl+Shift+R toggles)
  • /advisor clear — immediately discard every queued advisory (Ctrl+Shift+X; no confirmation)
  • /advisor config — choose the project or user-default WATCHDOG.yml editor; each also edits its own WATCHDOG.md attention instructions
  • /advisor help — command, shortcut, queue, and pause behavior reference

Security and privacy

Read this before installing the extension. An advisor starts enabled and is a second agent that watches your session, so it has real data-flow, cost, and trust implications.

Your session content is sent to the advisor's model provider, by two separate routes. If model: names a different provider than your main session, all of this goes to a second vendor.

  1. The per-turn feed (src/advisor/observations.ts), which is deliberately compact rather than a transcript dump:

    Included Form
    your messages verbatim within the recent window; oversized text may be shortened
    assistant replies verbatim within the recent window; oversized text may be shortened
    assistant reasoning excluded by default; available text included only with includePrimaryThinking: true
    tool calls tool name + one primary argument, truncated to 120 chars (so file paths, commands, grep patterns, URLs)
    successful tool results status and size only — ⇒ ok · 31 lines, 1204 characters, no body
    failed tool results status, size, and the first line of the error
    edit/write results fenced unified diff (8 lines of context, max 200 lines); subject to the context window and shortening
    your ! bash runs command preview + exit status + size, no output; !! runs stay excluded
    peer, extension, and advisor messages labeled previews up to 500 chars; messages hidden in the UI are skipped

    So ordinary file reads and command output do not leave as content — but your own prompts and applied diffs can. A context budget is not a secret-redaction policy.

  2. The advisor's own tool calls. It holds read/grep/glob by default and uses them to check claims, so it can read project files directly. Those results enter the advisor's bounded context and can go to its provider, independent of what the digest summarizes. Large results may be shortened before the next model request; older results expire with the rest of the conversation.

An advisor without an explicit model: follows the active chat provider. If you choose a different advisor model, session content also goes to that provider. To stop route 2 entirely, set tools: [], which grants no investigative tools (the advisor keeps advise).

Advisors can be granted write access, and are not sandboxed. tools: accepts edit, write, and bash in addition to the read-only default set. An advisor is a full pi agent, so granting those gives a model that runs automatically, with no turn-by-turn confirmation from you, the ability to modify files and execute commands in your working directory. The default (read, grep, glob) is read-only and is what you want unless you have a specific reason otherwise.

The advisor reads untrusted content, and there is no output quarantine. Error text, edit diffs, and anything it reads with its own tools may contain text crafted to manipulate a model. A manipulated advisor can put arbitrary text into your primary agent's context via advise().

Pi stores each note as a distinct customType: "advisor" message and the TUI shows it in a full-width Advisor card. At the provider boundary, however, pi currently converts all extension custom messages to the model's user role and does not forward customType. The extension therefore adds primary-system context saying that messages wrapped in <advisory> come from a separate AI advisor watching the session and are not authored by the user. It also explains one transcript behavior: if a late advisory causes a second completion after a completed response, that newer response may scroll the preceding one out of view and should stand on its own without assuming the preceding response was read. Neither sentence tells the primary whether or how to act on the advisor's technical claim.

Notes also carry guidance="weigh, don't blindly obey". That wording can reduce blind compliance, but neither it nor the visible card is a security boundary or output quarantine. Combined with the point above, a prompt-injection payload reaching an advisor that holds bash is a genuine risk. Keep advisors read-only.

Cost and rate limits. Each advisor is a live model session prompted roughly once per primary turn, so it consumes tokens continuously against whatever credentials that provider uses — a second, ongoing bill alongside your main session, per advisor.

Scope of what's watched. Advisors run in main sessions by default (main:) and not in subagent processes (subagents:). Both are configurable; see the subagent-detection note above for the caveat about how child processes are identified.

Releasing

Maintainers publish by pushing a version tag such as v0.2.2. GitHub Actions checks and packs the release, then publishes to npm through trusted publishing; no npm login or token is needed for the person pushing the tag. Stable releases use latest, prereleases use next. See the release guide for versioning, signed tags, and one-time owner setup.

Layout

  • src/index.ts — extension wiring: config discovery, lifecycle events, the pi.sendMessage host bridge
  • src/advisor/orchestrator.ts — one ActiveAdvisor per config, per-advisor delta cursor, batching/WIP inference, channel routing, immune-turn window
  • src/advisor/advise-logic.ts — AdviseState, severity ranking, resolveAdvisorDeliveryChannel
  • src/advisor/advise-tool.ts — the advise tool, with the emission guard gating at its boundary
  • src/advisor/context-window.ts — per-request rolling memory budget, safe exchange eviction, and explicit shortening
  • src/advisor/usage.ts — finalized-response usage accounting, independent of retained history
  • src/advisor/emission-guard.ts — noise filter + one-note-per-update budget
  • src/advisor/session-history-format.ts — stream-view transcript rendering and one-line tool summaries
  • src/advisor/observations.ts — the advisor's skim: one card per message or tool call, heading-preserving shortening
  • src/advisor/delta-render.ts — per-batch rendering and pairing tool calls with results
  • src/advisor/watchdog-config.ts — WATCHDOG.yml/.md discovery and merge
  • src/prompts/ — advisor system prompt and tool description

License

MIT. Includes MIT-licensed material from oh-my-pi (© Mario Zechner, Can Bölük, Stencil Labs, Inc.) — see LICENSE and PROVENANCE.md for the file-level breakdown of what is copied, ported, and original.