pi-grill

Dependency-driven design interview for the pi coding agent. Asynchronous TUI panel, JSON state source, skip/note support, and a final implementation plan.

Packages

Package details

extension

Install pi-grill from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-grill
Package
pi-grill
Version
0.4.1
Published
Aug 18, 2026
Downloads
780/mo · 36/wk
Author
luw2007
License
MIT
Types
extension
Size
96.1 KB
Dependencies
0 dependencies · 3 peers
Pi manifest JSON
{
  "extensions": [
    "./grill.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-grill

A dependency-driven design interview for the pi coding agent.

The agent interviews you about a plan or design one decision at a time, then writes an implementation plan once you confirm. The panel is asynchronous: publishing questions never blocks the agent, so it keeps investigating while you answer.

pi-grill demo: answer, refine, note, skip, custom answer, converge, plan

One real take: answer the recommended option → the agent instantly follows up → send an off-script note (Ctrl+N) → skip a question (Ctrl+S) → type a custom answer → confirm convergence → a plan lands in docs/plans/ with the full interview transcript.

Install

pi install git:github.com/luw2007/pi-grill@v0.3.0

Then restart pi or run /reload. To try it without installing: pi -e git:github.com/luw2007/pi-grill.

Usage

/grill port the review command to a pi extension

The agent then calls the grill_ask tool with one or more questions. They appear in a persistent TUI panel: a ledger of every question on the left, the selected question on the right.

Keys Action
/ (k / j) Move between options or ledger rows
(h) Previous question in the ledger
/ Enter (l) Confirm the selected option
Tab / Shift+Tab Switch between the ledger and the answer pane
Ctrl+1Ctrl+9, Ctrl+0 Jump straight to ledger question 1–10
Ctrl+S Skip the current question
Ctrl+N Add a note for the agent, not tied to any question
? Toggle the in-panel key help
Ctrl+Alt+G Toggle the active Grill panel
Esc / q Hide the panel; it reopens automatically for a new question
Ctrl+C / Ctrl+D Return focus to the editor, then abort / shut down

Terminal note: Ctrl+1Ctrl+0 and Ctrl+Alt+G need a terminal with the kitty keyboard protocol or xterm modifyOtherKeys (ghostty, kitty, WezTerm, iTerm2 — pi negotiates this automatically). In Terminal.app or default tmux those chords have no distinct encoding and arrive as plain keystrokes: use the arrow keys to reach ledger rows, and rebind the toggle via toggleShortcut in the configuration.

Ordinary options commit on Enter. Options marked requiresText, and the built-in Something else (type it), open a mandatory text field first.

On short terminals the panel fits itself to the window: the ledger shrinks first, and if content still overflows, the middle is elided with a … N lines hidden marker so the answer area and footer stay visible and interactive.

After a successful answer or skip, the panel hides while the agent continues investigating. Publishing a new question reopens it, selects that batch's current question, and scrolls the ledger until it is visible.

Commands

Command Action
/grill <idea> Start or resume an interview for that idea
/grill-panel Reopen and focus the panel after Esc

Skips, notes, and changing your mind

  • Ctrl+S marks a question skipped. Skipped questions do not block convergence and can still be answered later.
  • Ctrl+N records a free-form note. Notes are appended to the state, steered to the agent immediately, and preserved in the final plan.
  • Any answered or skipped question can be reopened and overwritten; the ledger keeps the latest answer.

Ending a session

Convergence is dependency-driven: once no pending or current questions and no unresolved decision dependencies remain, the agent asks a final confirmation. Section coverage is never a convergence requirement. Confirming writes the plan into the first existing directory among docs/plans/ and plans/, then closes the panel and clears the status widget. The plan derives its body headings and their order freely from substantive content—there is no fixed heading pool, required order, minimum section count, empty sections, or N/A placeholders—and always ends with a complete ## Interview transcript. The JSON state file is kept — rerun /grill with the same description to resume, or delete it yourself.

Declining the confirmation keeps the interview alive: the panel reopens with focus, and the final question stays re-answerable — answering it again with a converge keyword (default confirm, converge, yes, 确认, 生成) asks for confirmation again.

Tool contract

grill_ask takes a batch of questions and returns immediately:

{
  "questions": [
    {
      "id": "Q1",
      "section": "4. Design",          // free-form grouping label
      "question": "Which storage layer?",
      "context": "Why this matters and what the answer decides.",
      "options": [
        { "value": "sqlite", "label": "SQLite", "description": "…",
          "recommended": true, "recommendationReason": "…" },
        { "value": "other", "label": "Something custom", "requiresText": true }
      ],
      "recommended": "sqlite"
    }
  ],
  "converge": false
}

Answers arrive back asynchronously as a grill-answers custom message (batched within 500 ms), and notes as grill-note. Each event carries the batch plus an incremental open-questions summary; the full state summary stays on the grill_ask result. The section value only groups and indexes ledger questions; it does not constrain or decide which headings appear in the plan.

State

Each session has a single JSON state source under <tmpdir>/grill/<project>-<cwd-digest>/<hash>.json (the digest keeps same-named projects apart), plus an HTML mirror rendered from it. The JSON is authoritative: the panel, the status widget, and the plan all derive from it. The section index is a derived section → [{ id, status }] projection of questions; it is not persisted as a second source of truth.

The state deliberately lives in the OS tmpdir: it is interview scratch, not a durable artifact. macOS may purge it after a few days without access — a long-paused interview may need a fresh /grill run.

The state is schemaVersion 4. Upgrades are not backward compatible: a state file from an older schema, or one with missing or conflicting required fields, is treated as corrupt. pi-grill refuses to load it, leaves the file untouched, and asks you to delete or repair it.

Configuration

Optional, at ~/.pi/agent/grill.config.json:

{
  "convergeKeywords": ["confirm", "converge", "yes"],
  "optionScrollThreshold": 8,
  "toggleShortcut": "ctrl+alt+g"
}

optionScrollThreshold is how many option blocks are shown before the answer pane starts scrolling (1–100, default 8). toggleShortcut controls the panel shortcut using Pi key syntax (default ctrl+alt+g); choose a key that does not collide with a Pi built-in or another extension. An invalid config is reported and safe defaults are used.

Alternate entrypoint

grill-omp.ts re-exports the same extension for hosts that load a differently named entrypoint:

export { default } from "./grill.ts";

The implementation stays single-sourced in grill.ts; the re-export exists only so such a host can point at grill-omp.ts without a second copy of the code. If you install with pi install, ignore it and use grill.ts.

Notes for developers

Runs entirely in the pi TUI via @earendil-works/pi-tui. No network access and no npm runtime dependencies — @earendil-works/pi-tui and typebox are provided by pi as peer dependencies.

bun test
bunx tsc -p tsconfig.tests.json
npm run e2e   # full-loop regression against a real pi TUI with a deterministic local mock model
npm run e2e:omp   # OMP host-contract regression (crash class: overlay handles missing focus/unfocus); skips if omp is absent

Host-contract invariants (learned from real pi/OMP behaviour; changes must respect them):

  • A focused overlay is pi's input terminal stop: it starves every app keybinding and every registerShortcut chord. Any shortcut that must work while the panel is focused has to be handled inside the panel component too (that is why the toggle chord appears in both places).
  • ctrl+digit chords have no legacy terminal encoding — only kitty CSI-u / modifyOtherKeys forms exist. Never bind a feature to them without a fallback path.
  • OMP's native runtime can deviate from its bundled pi typings. Verified on OMP 17.3.4: showOverlay handles expose only { hide, setHidden, isHidden } — no focus/unfocus — and the host input dispatch has no try/catch around handleInput, so one unguarded handle call crashes the whole OMP process. Typings are not acceptance evidence for OMP; verify against the live host and guard every handle method defensively (focus?.(), unfocus?.()).
  • On OMP, sendUserMessage(..., { deliverAs: "followUp" }) while idle queues the message but does not start a turn (observed 17.3.4 with an isolated mock-model repro); the interview begins on the user's next input. On pi it triggers immediately.
  • Extension messages: only content reaches the model; details is UI/transcript-only. sendUserMessage must always pass deliverAs: "followUp" — an idle check races the agent starting a run and drops the message.
  • Two sessions sharing one state file coordinate via the watcher and monotonic revisions, but truly simultaneous commits are last-rename-wins; there is deliberately no file locking.

Acknowledgements

  • mattpocock/skills — grilling inspired the design-interview approach: resolve a plan through focused, sequential questions.
  • edlsh/pi-ask-user informed the Pi-native structured-question interaction model, including multiple-choice and free-text answers.

pi-grill is an independent implementation; it does not include code from either project.

License

MIT