You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Power users want to wire their own tools onto the composer without cloning the source repo. The Desktop App ships three prebuilt composer toggles (plan/build, caveman, orchestrate), but there is no way for a user to add their own button that runs a slash command, fires a whole prompt, or triggers a favourite extension. Beyond single buttons, users want to own their composer — arrange tools on a grid, choose colors and whether titles show, ship a default, and use their own LLM to help build it.
Solution
Two concepts with a one-way dependency (see CONTEXT.md):
Chassis Action — a user-defined, wired behavior attached to the composer. Effects: submit (one-shot: slash command / extension command / canned prompt), wrap (sticky: frame the outgoing prompt via {{input}}), and later reminder (sticky: a session system-reminder via a companion skill).
Composer Layout — a user-authored arrangement and styling of composer controls (built-in controls and Chassis Actions) on a grid: position, color, title visibility. Consumes actions; actions know nothing about layout.
Delivered in phases so the model is proven before the heavy UX is built:
Phase 2:reminder effect (+ companion skill), an extension picker in the create form, and per-project-folder scoping for action definitions and sticky activation.
Phase 3: the Composer Layout grid editor (snap/position/color/title, ship-with-a-default) and the LLM builder assistant (describe an action/layout, your model emits the config). Both require their own design pass before implementation.
User Stories
Phase 1 — Chassis Actions MVP
As a Desktop App user, I want to create a button that sends a slash command, so that I trigger a workflow in one click.
As a user, I want a button that triggers an installed extension, so that I needn't remember its slash command.
As a user, I want a button that sends a whole canned prompt, so that I kick off a repeated task instantly.
As a user, I want a sticky toggle that frames whatever I type with a reusable wrapper, so that I apply a persistent stance across messages.
As a user, I want to name each button and choose whether its title shows, so that my composer stays readable.
As a user, I want actions defined once and available in every thread, so that I don't re-create them per conversation.
As a user, I want a sticky wrap to stay on across messages until I turn it off.
As a user, I want turning on one wrap to turn off any other, so that I never get nested framings.
As a user, I want a sticky wrap to compose predictably with plan mode (wrap inside, plan outside), so plan mode stays authoritative.
As a user, I want a one-shot button to send immediately and leave my unsent draft untouched.
As a user, I want actions to persist across restarts.
As a user, I want a malformed action dropped, not fatal, so a bad entry can't brick the composer.
As a user, I want to add, edit, and delete actions in Settings.
As a user, I want my buttons to appear across the in-thread, new-thread, and pending composer surfaces.
As a user, I want one-shot actions to work whether the session is idle or running (steering).
Phase 2 — reminder, extension picker, per-folder
As a user, I want a sticky action that injects a standing system reminder into the session, so that I keep a behavior active without re-typing it.
As a user, I want to pick an installed extension's command from a list when creating an action, so that I don't hand-type slash text.
As a user, I want different actions and active toggles per project folder, so that each project has the right tools.
As a user, I want my reminder to be picked up automatically when a session starts, so that I configure it once.
As a user, I want to drag my controls onto a grid and position them how I like, so that I build my own composer.
As a user, I want to set each control's color, so that my composer is visually mine.
As a user, I want to show or hide each control's title, so that I balance density and clarity.
As a user, I want the app to ship with a sensible default layout, so that it's usable out of the box.
As a user, I want to describe a button or layout in words and have my own LLM generate the config, so that I build tools without learning the schema.
As a user, I want the generated config validated and previewed before it's saved, so that a bad generation can't break my composer.
Implementation Decisions
Two concepts, one-way dependency. Chassis Action = behavior only (no position/color; one showLabel flag in MVP). Composer Layout consumes actions.
Effects ride existing seams; no pi runtime coupling in MVP. No programmatic extension-trigger API exists in pi — extension commands are slash commands. submit uses the existing composer submit path; extension is a submit of the extension's slash command. wrap extends the plan-mode prompt-wrapping logic.
Schema (MVP). Definitions in ~/.pi/agent/chassis/state.json (global per-user), mirroring caveman state:
oneShot↔submit, sticky↔wrap. {{input}} is the only template token.
Scope. Registry global per-user. Sticky activation app-global for MVP → per-project-folder later, never per-thread (ADR 0004); activation lives in app-global desktop state, not the definitions file.
Composition. One active wrap at a time (radio); applied inside plan mode: planModePrompt(userWrap(rawText)).
One render seam. Every action renders via a single ChassisActionControl in the composer control row, alongside prebuilt toggles. No generic slot/contract for built-in controls until the grid forces it.
Validation. Validate on load; drop malformed actions.
Authoring UI. Settings → Actions section for MVP. The dedicated "build your own composer" surface arrives with the Phase 3 grid.
Phase 2 — reminder. Adds effect: { type: "reminder", text }; a companion skill reads chassis state at session start (caveman pattern). This is the first piece with a shippable skill artifact + install step.
Phase 2 — extension picker. The create form lists installed runtime commands (skills/extensions) and writes the chosen command's slash text as a submit effect. No new runtime API.
Phase 2 — per-folder scope. Action definitions and sticky activation key off the active project folder/workspace rather than app-global.
Phase 3 — Composer Layout grid (design required). Open questions: grid data model and persistence, snap/drag/resize behavior, per-control color system, how built-in controls and Chassis Actions become uniform slottable units, and the shipped default layout. Needs a design pass before implementation.
Phase 3 — LLM builder assistant (design required). Open questions: where it lives (in the layout surface), which model it uses (user's configured model), the describe→config→validate→preview flow, and safety/validation of generated config. Needs a design pass.
Testing Decisions
Test external behavior at the highest existing seam.
State parse/validate — pure parseChassisState(json) -> { actions, dropped }, unit (vitest), no Electron.
Effect composition — pure function extending composer-mode.ts: raw text + plan on/off + active wrap → composed prompt (wrap inside plan, single-wrap radio). Unit. Prior art: buildPlanModePrompt tests.
One-shot submit — e2e live: payload lands in transcript, draft preserved, works idle/running.
Composer render + Settings CRUD — e2e core: created action appears in Settings and renders across in-thread/new-thread/pending surfaces.
Phase 2 reminder — verify the companion skill picks up reminder state at session start (live/runtime lane).
Phase 3 — testing decisions deferred to each design pass (the grid and builder need their own seams defined first).
Out of Scope
Stacking multiple simultaneous wraps.
Migrating the prebuilt toggles (plan/build, caveman, orchestrate) into the registry — they stay separate and per-thread.
Structured args for extension commands (payload is raw text).
Detailed Phase 3 implementation specs (grid, builder) — these are gated behind design issues, not specified here.
Further Notes
Recon confirmed no programmatic extension-trigger API in pi; the only invocation path is sending a command's slash text through the submit seam, which is why extension collapses into submit.
Decisions captured via grill-with-docs; terminology in CONTEXT.md, scope-axis rationale in docs/adr/0004-chassis-scope-axis.md.
Problem Statement
Power users want to wire their own tools onto the composer without cloning the source repo. The Desktop App ships three prebuilt composer toggles (plan/build, caveman, orchestrate), but there is no way for a user to add their own button that runs a slash command, fires a whole prompt, or triggers a favourite extension. Beyond single buttons, users want to own their composer — arrange tools on a grid, choose colors and whether titles show, ship a default, and use their own LLM to help build it.
Solution
Two concepts with a one-way dependency (see CONTEXT.md):
submit(one-shot: slash command / extension command / canned prompt),wrap(sticky: frame the outgoing prompt via{{input}}), and laterreminder(sticky: a session system-reminder via a companion skill).Delivered in phases so the model is proven before the heavy UX is built:
submit+wrap, authored in a Settings → Actions section, rendered as buttons alongside the prebuilt toggles. App-global. No layout, no builder. (Issues Chassis Actions: one-shot submit action end-to-end #46, Chassis Actions: sticky wrap action end-to-end #47, Chassis Actions: edit, delete, and showLabel management #48.)remindereffect (+ companion skill), anextensionpicker in the create form, and per-project-folder scoping for action definitions and sticky activation.User Stories
Phase 1 — Chassis Actions MVP
Phase 2 — reminder, extension picker, per-folder
Phase 3 — Composer Layout grid + builder assistant
Implementation Decisions
showLabelflag in MVP). Composer Layout consumes actions.piruntime coupling in MVP. No programmatic extension-trigger API exists inpi— extension commands are slash commands.submituses the existing composer submit path;extensionis asubmitof the extension's slash command.wrapextends the plan-mode prompt-wrapping logic.~/.pi/agent/chassis/state.json(global per-user), mirroring caveman state:{ "version": 1, "actions": [ { "id": "uuid", "label": "Security audit", "showLabel": true, "trigger": "oneShot", "effect": { "type": "submit", "text": "/security-scan" } } // | { ..., "trigger": "sticky", "effect": { "type": "wrap", "template": "...{{input}}..." } } ] }oneShot↔submit,sticky↔wrap.{{input}}is the only template token.planModePrompt(userWrap(rawText)).ChassisActionControlin the composer control row, alongside prebuilt toggles. No generic slot/contract for built-in controls until the grid forces it.effect: { type: "reminder", text }; a companion skill reads chassis state at session start (caveman pattern). This is the first piece with a shippable skill artifact + install step.submiteffect. No new runtime API.Testing Decisions
Test external behavior at the highest existing seam.
parseChassisState(json) -> { actions, dropped }, unit (vitest), no Electron.composer-mode.ts: raw text + plan on/off + active wrap → composed prompt (wrap inside plan, single-wrap radio). Unit. Prior art:buildPlanModePrompttests.live: payload lands in transcript, draft preserved, works idle/running.core: created action appears in Settings and renders across in-thread/new-thread/pending surfaces.Out of Scope
Further Notes
pi; the only invocation path is sending a command's slash text through the submit seam, which is whyextensioncollapses intosubmit.