@giladbarnea/pi-user-agents 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/INTERNALS.md ADDED
@@ -0,0 +1,65 @@
1
+ # Internals
2
+
3
+ Design notes for contributors. User-facing behavior is documented in [README.md](README.md); the command grammar is specified in [PARSER_SPEC.md](PARSER_SPEC.md).
4
+
5
+ ## Result delivery — Pi SDK mental model
6
+
7
+ How a finished run reaches (or hides from) the main agent rests on two Pi SDK channels with very different context semantics:
8
+
9
+ - **`pi.sendMessage(message, { deliverAs, triggerTurn })`** — custom messages ALWAYS reach the main agent's LLM context eventually. The options form a first-match-wins chain (see `sendCustomMessage` in pi's `dist/core/agent-session.js`):
10
+ 1. `deliverAs: "nextTurn"` → buffered in memory, spliced in alongside the user's next prompt; `triggerTurn` ignored; not persisted — lost if the session ends first. Still reaches the agent eventually.
11
+ 2. Agent streaming → `"followUp"` queues for after the run finishes; `"steer"` (default) injects after the current tool batch, before the next LLM call. `triggerTurn` ignored. Queues drain before `agent_end` — messages are never dropped.
12
+ 3. Agent idle + `triggerTurn: true` → appended and an LLM turn starts immediately.
13
+ 4. Agent idle otherwise → silently appended to context; the agent sees it on whatever turn comes next.
14
+
15
+ In short: `deliverAs` only matters while streaming; `triggerTurn` only matters while idle.
16
+
17
+ - **`pi.appendEntry(customType, data)` + `pi.registerEntryRenderer`** — the only channel that renders in the transcript while staying out of LLM context *forever*. Entries are persisted in the session file and re-rendered on reload. The entry's `data` is optional in the type — renderers must guard it.
18
+
19
+ This extension maps the two extremes onto the `-j` flag: the default posts the result card via `appendEntry` (main agent structurally oblivious), and `-j` posts a visible custom message with `{ triggerTurn: true }` (steered mid-turn or answered immediately). Both channels share one card renderer in `transcript.ts`.
20
+
21
+ Immediate `-j` delivery and late `j join` delivery share one canonical message builder. Its `display` option prevents a second visible result card. Both paths use the same selected conversation and `{ triggerTurn: true }` delivery. For a run launched without `-j`, the context message stays hidden at join time because the TUI-only result card is already visible.
22
+
23
+ ## Joined message format
24
+
25
+ The joined text starts with this sentence:
26
+
27
+ > The user has dispatched a background sub-agent with a task. The sub-agent is done. The following is the back and forth between them:
28
+
29
+ A plain `<user_agent>` tag follows it. The tag contains only the model and inherited-context attributes. Its body contains numbered `<user_message>` and `<assistant_response>` tags, indented by two spaces. The first user message contains the original task without the extension's internal process prefix.
30
+
31
+ One selection rule builds the body. It keeps every user message. Before each next user message, it keeps only the latest assistant response. It applies the same rule after the final user message. Tool calls, thinking, tool results, and other message roles stay out.
32
+
33
+ ## Runtime sharing
34
+
35
+ Child agent sessions share the parent's `ModelRuntime` instance. This is critical because extension-registered providers (like `claude-bridge`) use a global `Symbol.for` dedup guard — they register once per process and skip subsequent loads. A child session with its own fresh runtime would never receive the provider, leaving it without auth for bridge-backed models.
36
+
37
+ The shared runtime is extracted from the parent's `ModelRegistry` facade (`ctx.modelRegistry`) which wraps a `ModelRuntime` as a plain `.runtime` property.
38
+
39
+ ## Model name & alias resolution
40
+
41
+ User-defined model aliases (`name` in `models.json` `modelOverrides`) take precedence: `-m opus` resolves to `claude-bridge/claude-opus-4-6` directly, without ambiguity. The extension checks for an exact name match before falling through to the SDK's `resolveCliModel`, which only checks `model.id` (never `model.name`) and otherwise falls back to substring matching — where "opus" collides with dozens of built-in models across the catalog.
42
+
43
+ > **Note (known issue):** `enabledModels` (i.e., “Scoped models”) currently have no special role in how the extension resolves models. They should be promoted to the same precedence level as user-defined model names in a future fix.
44
+
45
+ Exact names should be unique: duplicate aliases silently select the first model, and an exact alias match currently ignores `--provider`, even if it names another provider.
46
+
47
+ ## Editor: coloring and completion details
48
+
49
+ ### Semantic command coloring
50
+
51
+ While you type, the editor paints `/agent` lines from the same semantic scan that drives submission parsing (see `PARSER_SPEC.md` §11): the command in the theme's `accent`, recognized leading options in `syntaxKeyword`, and valid values (quotes included) in `syntaxString`. Invalid thinking levels, unresolved model IDs or aliases, and unknown provider or tool names use `error`, as do explicitly blocked Pi options such as `-c`/`--continue` and options typed after the task prose has begun — along with their values. Validation comes from Pi's argument parser, model resolver, and live catalogs rather than a second editor grammar. Prose, unknown dash-words, quoted spans, and escaped words stay plain. All colors come from the active Pi theme, so custom themes and hot reload apply automatically.
52
+
53
+ ### Eager argument completion
54
+
55
+ Type `/agent -` and the option menu opens immediately — no Tab is required. Short and long aliases appear separately, while using either one hides the whole semantic option from later menus. Once you begin the task prose, quoted text, or an escaped dash, the option menu stays off.
56
+
57
+ The finite value menus are thinking levels, live providers, live models, and the session's live tool catalog. Models from the configured `enabledModels` scope lead the unfiltered model menu; once you type a query, Pi's fuzzy relevance controls the order and scope only breaks equal-score ties. `--provider` narrows the model menu for either `-m` or `--model`. A completed boolean, thinking level, provider, or model leaves exactly one trailing space.
58
+
59
+ `--tools`/`-t` and `--exclude-tools`/`-xt` complete one comma-delimited segment at a time. Accepting `grep` produces `--tools grep│` — the menu closes without inserting a space or comma. Type `,` to open the next segment immediately; already selected tools are omitted. Type a normal space when the tool list is finished.
60
+
61
+ Free-form values are guarded instead of guessed. Selecting `--system-prompt` produces `/agent --system-prompt "│" `, with the cursor between the quotes and the trailing separator already preserved.
62
+
63
+ `--extension`/`-e`, `--skill`, and `--prompt-template` are path-valued. Accepting one from the option menu eagerly opens filesystem completion inside its guarded quotes, and manually typing an exact path option plus its value-boundary space opens the same menu without Tab. Directories continue traversal inside the quotes; accepting a file finishes the quoted path and moves the cursor after the outer space. Completion in the middle changes only the active fragment and preserves everything after it.
64
+
65
+ Installed skills and prompt templates also appear as named shortcuts from public `pi.getCommands()` entries. Selecting one inserts its public `sourceInfo.path`, while arbitrary filesystem entries remain available in the same menu. Extensions stay filesystem-only because command-backed extension discovery is incomplete. No theme path menu is offered: singular `--theme <path>` remains blocked, while `--no-themes` remains supported and is forwarded to the child.
package/PARSER_SPEC.md ADDED
@@ -0,0 +1,264 @@
1
+ # `/agent` command-line parser — specification
2
+
3
+ This is the authoritative spec for how the shared command-line engine interprets
4
+ submitted `/agent` input and pre-submit editor input. Runtime parsing
5
+ and eager autocomplete both derive option names, arity, aliases, roles, and value domains
6
+ from the declaration in `command-line.ts`.
7
+
8
+ ## 1. What the parser produces
9
+
10
+ ```ts
11
+ type ParsedAgentCommand = {
12
+ isolate: boolean; // -i / --isolate
13
+ context: boolean; // -j / --join
14
+ forwardedArgs: string[]; // recognized pi option tokens (+values) in input order, e.g. ["--thinking","high"]
15
+ task: string; // the prose prompt (backslash escapes applied, quotes kept). Never empty.
16
+ warnings: string[]; // advisory messages (see §5). May be empty.
17
+ };
18
+ ```
19
+
20
+ `forwardedArgs` contains **only** known, non-blocked pi options — never the extension's own
21
+ options, never unknown/third-party tokens. It is later mapped to child-session inputs by
22
+ `resolveForwardedOptions` (unchanged in spirit, but the unknown-flag path is removed).
23
+
24
+ ## 2. The state machine (one-way: args → prose)
25
+
26
+ Read whitespace-delimited tokens from the front. Start in **args mode**.
27
+
28
+ - In args mode, classify each token (§4). A recognized option is consumed (with its value if it
29
+ takes one) and we stay in args mode.
30
+ - The **first** token that is not a recognized option flips us to **prose mode**, permanently.
31
+ There is no path back to args mode.
32
+ - **Prose** is the remainder of the original input starting at the flip token, kept verbatim
33
+ except for backslash-escape processing (§6). Whitespace and quotes are preserved exactly.
34
+
35
+ If options are consumed and nothing is left, that is a usage error (a task is required).
36
+ When the very first token is prose, the entire original input is the task, including any
37
+ leading whitespace (unchanged from today).
38
+
39
+ ## 3. Recognized options
40
+
41
+ ### 3a. Extension-owned and normalized options
42
+ | Token(s) | Arity | Effect |
43
+ |---|---|---|
44
+ | `-m`, `--model` | value | Added to `forwardedArgs` as canonical `--model <value>` |
45
+ | `-i`, `--isolate` | boolean | Consumed here; `isolate = true` |
46
+ | `-j`, `--join` | boolean | Consumed here; `context = true` |
47
+
48
+ `-m` and `--model` are one semantic option. Both resolve once through Pi's
49
+ `resolveCliModel()` path, and a preceding `--provider` scopes either spelling. `-j` is the
50
+ extension's join option. A model option with no value and no remaining task ends in the normal
51
+ **usage error**.
52
+
53
+ ### 3b. Known pi options — declared statically; added to `forwardedArgs`
54
+ **Value-taking** (consume the next token as the value):
55
+ `--provider`, `--api-key`, `--system-prompt`, `--append-system-prompt`, `--mode`,
56
+ `--name`/`-n`, `--session`, `--session-id`, `--fork`, `--session-dir`, `--tools`/`-t`,
57
+ `--exclude-tools`/`-xt`, `--thinking`, `--extension`/`-e`, `--skill`, `--prompt-template`.
58
+
59
+ **Boolean** (consume no value):
60
+ `--resume`/`-r`, `--no-session`, `--no-tools`/`-nt`, `--no-builtin-tools`/`-nbt`,
61
+ `--no-extensions`/`-ne`, `--no-skills`/`-ns`, `--no-prompt-templates`/`-np`, `--no-themes`,
62
+ `--no-context-files`/`-nc`, `--verbose`, `--approve`/`-a`, `--no-approve`/`-na`, `--offline`,
63
+ `--print`/`-p`.
64
+
65
+ Notes:
66
+ - The shared declaration is a snapshot of `pi --help`; it is maintained by hand. There is no
67
+ runtime dependency on pi's parser for recognition or arity.
68
+ - **Reading a value** (applies to every value-taking option, including both model spellings):
69
+ the value is the token immediately after the option. If that token **begins with a quote char**
70
+ (§6a) and a matching close quote appears later, the value is the text between the quotes —
71
+ spanning internal whitespace — with the surrounding quotes **removed**; its content is literal
72
+ (no arg parsing, no backslash processing inside — WYSIWYG). Otherwise the value is the next
73
+ whitespace-delimited token, verbatim (consumed even if it looks like another option, e.g.
74
+ `--thinking --offline` sets thinking's value to `--offline`). There is **no** distinction between
75
+ "single-value" and "multi-value" options — every value-taking option reads its value this way, so
76
+ `-m gpt56s` ≡ `-m "gpt56s"` and `--system-prompt Psych` ≡ `--system-prompt "Psych"`.
77
+ The dequoted value is stored as **one element** in `forwardedArgs`
78
+ (e.g. `["--system-prompt", "I am psyched"]`), so pi's `parseArgs` maps it as a single value.
79
+ If a value opens with a quote but has no matching close, fall back to the whitespace-delimited token.
80
+ - `--print`/`-p` is modeled as **boolean** here (pi's optional-value form would eat prose).
81
+
82
+ Value examples:
83
+ - `--system-prompt "I am psyched"` → value `I am psyched`.
84
+ - `--system-prompt "talk about -m modelname arg"` → value `talk about -m modelname arg` (inner `-m` not parsed).
85
+ - `--system-prompt "talk about \-m arg"` → value `talk about \-m arg` (backslash kept; no recursion).
86
+ - `-m "gpt56s"` → `forwardedArgs` contains `--model`, `gpt56s`.
87
+
88
+ ### 3c. Blocked pi options — rejected
89
+ `-c`/`--continue`, `--theme <path>`, `--models`, `--export`, `--list-models`, `-h`/`--help`,
90
+ `-v`/`--version`.
91
+ Appearing in **args mode** (leading) → hard **error**:
92
+ `"/agent does not support <opt>; it would disrupt the background agent run."`
93
+
94
+ Blocked options stay declared so prose advisories remain truthful. In particular, singular
95
+ `--theme` is declared value-taking: `do it --theme ./theme.json` warns about
96
+ `--theme ./theme.json`, not just the option name. `--no-themes` is a separate supported
97
+ boolean and remains forwarded.
98
+
99
+ ## 4. Classifying a leading token (args mode), in order
100
+ 1. **Escaped** (starts with a quote char, or is a backslash-before-dash word) → prose; **flip**. No warning. (§6)
101
+ 2. **Extension-owned or normalized option** (§3a) → consume (+ model value); stay in args mode.
102
+ 3. **Blocked pi option** (§3c) → **error**.
103
+ 4. **Known pi option** (§3b) → consume (+ value if value-taking); push raw token(s) to `forwardedArgs`; stay in args mode.
104
+ 5. **Anything else** — unknown `--foo` / `-x`, or a non-dash word → **flip to prose**; no warning.
105
+
106
+ ## 5. Warnings (prose mode only) — exactly one kind
107
+ While in prose mode, if an **unescaped, unquoted** token is a **recognized** option
108
+ (extension §3a, known §3b, or blocked §3c), append this advisory:
109
+
110
+ > ``<opt> [<value>] was included in the prompt body. To specify an argument or an option, place it at the beginning of the command input: /<command> <opt> [<value>] <first-prose-word> …``
111
+
112
+ - `<value>` appears only for value-taking options; it is the token that immediately follows in the input.
113
+ - `<first-prose-word>` is the first whitespace-delimited word of the prose.
114
+ - Unknown arg-looking tokens in prose (`-g`, `--doesnotexist`) → **no** warning.
115
+ - Quoted or backslash-escaped option-looking tokens → **no** warning.
116
+ - Warnings never change the prose text.
117
+
118
+ Worked examples:
119
+ - `hello world -m gpt56s` → task = whole string; warn `-m gpt56s`, suggest `/agent -m gpt56s hello …`.
120
+ - `-m gpt56s hello --thinking high world` → `-m gpt56s` parsed; task = `hello --thinking high world`;
121
+ warn `--thinking high`, suggest `/agent --thinking high hello …`.
122
+ - `-g badflagname hello world` → `-g` unknown → task = whole string; **no** warning.
123
+ - `hello world -g badflagname` → task = whole string; **no** warning.
124
+ - `--doesnotexist this is my message` → `--doesnotexist` unknown → task = whole string; **no** warning.
125
+
126
+ ## 6. Escaping — opt a token out of arg parsing. NEVER recurses.
127
+
128
+ ### 6a. Quotes — kept in prose; stripped ONLY when reading an option value
129
+ Quote chars: `` ` `` `'` `"` `‘` `’` `“` `”`. Matched pairs: `"…"`, `'…'`, `` `…` ``, `‘…’`, `“…”`.
130
+ - Inside a matched quote span: no token is treated as an option, and no warning fires.
131
+ - In **prose**, quotes are **kept verbatim** — they are ordinary characters that merely suppress
132
+ arg parsing. The one exception is when a quote span is being read as an **option's value**
133
+ (§3b): there the surrounding quotes are removed and the content becomes the value.
134
+ - Content inside quotes is literal; a backslash inside quotes is **not** processed (kept).
135
+ - A quoted span at the very start (not an option value) still flips to prose.
136
+ - An unmatched quote is just a literal character (no span, no effect).
137
+
138
+ Examples:
139
+ - `summarize "war and peace"` → task `summarize "war and peace"` (prose → quotes kept).
140
+ - `"\-m gpt56 hello"` (leading, not a value) → task `"\-m gpt56 hello"` (quotes kept, backslash kept, no arg parsing inside).
141
+ - `--system-prompt "war and peace"` → value `war and peace` (value → quotes stripped).
142
+
143
+ ### 6b. Backslash-before-dash — the backslash IS removed
144
+ A backslash at the start of a word whose next char is `-` escapes that word:
145
+ - Remove the leading `\`; the word (now starting with `-`) is prose. Flip to prose if in args mode;
146
+ if already in prose, still remove the backslash and emit no warning.
147
+ - Overly-cautious double form: after removing the leading `\` before `-`, if the next two chars are
148
+ `\-`, remove that `\` too. So `\-\-word` → `--word`. One backslash also suffices: `\--word` → `--word`.
149
+ - A backslash **inside quotes** is not processed (see §6a).
150
+ - A backslash NOT immediately before a `-` is left literal (not an escape).
151
+
152
+ Examples:
153
+ - `\-m` → `-m`; `\--thinking` → `--thinking`; `\-\-thinking` → `--thinking`.
154
+ - `hello agent, \-m "gpt56s" how are you?` → `hello agent, -m "gpt56s" how are you?`.
155
+ - `hello agent, \--thi"nking" high` → `hello agent, --thi"nking" high`.
156
+
157
+ ## 7. Application layer (`resolveForwardedOptions`) — simplified
158
+ `forwardedArgs` (known pi options only) is mapped to child-session inputs. The application
159
+ layer maps thinking, model/provider through `resolveCliModel`, tools/excludeTools/noTools,
160
+ and `resourceLoaderOptions` (system prompt, skills, extensions, prompt templates, and
161
+ supported no-* toggles including `--no-themes`). Singular `--theme` never reaches this
162
+ projection. There is no unknown-flag / `extensionFlagValues` path or runtime arity probe;
163
+ recognition and arity come from the shared declaration.
164
+
165
+ ## 8. Wiring
166
+ - `parseAgentCommand` is pure and returns `warnings`.
167
+ - `startUserAgent` emits each warning via `ctx.ui.notify(warning, "warning")` inside the existing
168
+ `if (ctx.hasUI)` block. Parse/blocked/usage errors keep flowing through `handleAgentCommand`'s
169
+ try/catch to `reportCommandError` (unchanged).
170
+
171
+ ## 9. Edge decisions (locked)
172
+ - Bare `-m` (no value) → usage error.
173
+ - Empty task after consuming options → usage error.
174
+ - Blocked option leading → hard error; in prose → warning.
175
+ - No `--` options terminator (backslash/quotes handle "task starts with a dash").
176
+ - Value-taking option with no following token → the option is still recorded (no value); no crash.
177
+ - Multiple known options in prose → one warning per occurrence, in order.
178
+
179
+ ## 10. Pre-submit cursor analysis and eager autocomplete
180
+
181
+ Cursor analysis recognizes only exact `/agent` command lines. It
182
+ preserves the active token's source span and reports either an option fragment, a value
183
+ fragment with its owning declaration, or no completion once parsing has entered terminal
184
+ prose. Quoted and backslash-escaped dashes therefore never reopen option completion.
185
+
186
+ Typing `-` at a valid option position explicitly opens the menu without Tab. A single `-`
187
+ may match short and long spellings; `--` matches long spellings only. Every alias is shown as
188
+ its own row, but using either spelling suppresses the whole semantic option thereafter.
189
+ Blocked and recognized-but-child-ineffective options remain parseable but are never
190
+ recommended.
191
+
192
+ The finite value domains implemented today are:
193
+
194
+ - thinking levels from the fixed Pi level set;
195
+ - unique providers from the live model registry;
196
+ - live canonical `provider/id` model references, filtered by a preceding `--provider` for
197
+ either model spelling;
198
+ - tool names from the live public `pi.getAllTools()` catalog for `--tools`/`-t` and
199
+ `--exclude-tools`/`-xt`.
200
+
201
+ Tool completion is local to the active comma-delimited segment. Accepting a tool replaces only
202
+ that segment and closes the menu without adding a space or comma. Typing `,` immediately opens
203
+ the next segment and excludes every tool already selected elsewhere in the value. The user
204
+ types a normal space to finish the complete tool list. Other non-path value options remain
205
+ guarded rather than pretending to have finite catalogs.
206
+
207
+ `--extension`/`-e`, `--skill`, and `--prompt-template` are path domains. Accepting one from
208
+ the option menu inserts the guarded slot and eagerly opens the shared filesystem pipeline.
209
+ Manually typing an exact path option and its value-boundary space eagerly opens the same
210
+ pipeline. Directories keep traversal inside the quotes; files finish after the single separator
211
+ outside the closing quote.
212
+
213
+ For `--skill` and `--prompt-template`, installed-resource shortcuts from public
214
+ `pi.getCommands()` entries are merged with native filesystem candidates. Shortcut filtering
215
+ uses the public command source (`skill` or `prompt`), and selection inserts the public
216
+ `sourceInfo.path` as one path value. Arbitrary filesystem fallback remains available.
217
+ `--extension` stays filesystem-only because command-backed extension discovery is incomplete.
218
+ Singular `--theme <path>` remains blocked and has no menu; `--no-themes` remains supported.
219
+
220
+ Accepting a boolean option, thinking level, provider, or model normalizes adjacent whitespace
221
+ to exactly one separator and puts the cursor after it. Tool values deliberately omit that
222
+ automatic separator until the user finishes the comma list. Accepting a guarded option puts
223
+ the cursor between the quotes while preserving the outer separator. Mid-line edits replace
224
+ only the active span and leave the suffix intact. Tab and Enter both chain finite value menus;
225
+ this includes Pi's asynchronous unique-result Tab path. An empty live domain produces Pi's
226
+ normal empty-picker outcome rather than fabricated candidates.
227
+
228
+ End-to-end editor tests use valid `.ts`, `SKILL.md`, and prompt Markdown fixtures for every path
229
+ option, including manual boundary eagerness, guarded recursive traversal, final cursor placement,
230
+ public shortcut insertion, filesystem fallback, and filesystem-only extension behavior.
231
+
232
+ ## 11. Semantic token scan and editor coloring
233
+
234
+ `scanAgentArguments` is the single shared semantic representation behind both runtime parsing
235
+ and editor coloring. It runs the §2 state machine once and emits, alongside the
236
+ `ParsedAgentCommand` projections, an ordered list of non-overlapping semantic tokens with
237
+ source spans:
238
+
239
+ - `option` — a recognized, non-blocked leading option (extension-owned or forwarded);
240
+ - `value` — the value consumed by a leading value-taking option. A quoted value's span
241
+ includes its surrounding quotes;
242
+ - `invalid-value` — a leading closed-set value rejected by the same canonical validation
243
+ used at submission and session creation: Pi's argument parser for thinking levels, the
244
+ child model resolver for model IDs and user-defined aliases, and the live provider and
245
+ tool catalogs;
246
+ - `blocked` — a blocked option in args mode, plus the value a blocked value-taking option
247
+ would consume (`--theme ./theme.json` yields two `blocked` tokens). Unlike
248
+ `parseAgentCommand`, the scan never throws: it records the first blocked token and keeps
249
+ scanning in args mode so later tokens still classify;
250
+ - `misplaced-option` / `misplaced-value` — the §5 advisory pair: a recognized, unquoted,
251
+ unescaped option in prose and the token its warning cites as the value. A misplaced value
252
+ that is itself a recognized option gets its own `misplaced-option` token instead.
253
+
254
+ Prose itself is never tokenized; unknown dash-words, quoted spans, and escaped words remain
255
+ plain. `parseAgentCommand` is a thin projection over the scan (blocked → hard error, blank
256
+ prose → usage error), so parsing and coloring cannot drift.
257
+
258
+ `scanAgentCommandLine` recognizes the same exact `/agent` prefix as
259
+ cursor analysis (§10) and shifts token spans into editor-line coordinates.
260
+
261
+ The editor coloring layer (`editor-coloring.ts`) maps token semantics onto Pi theme
262
+ variables — command `accent`, options `syntaxKeyword`, valid values `syntaxString`, and
263
+ invalid, blocked, or misplaced tokens `error` — by repainting the laid-out chunk text after
264
+ word-wrap layout, so editing, wrapping, cursor behavior, and autocomplete are untouched.
package/README.md ADDED
@@ -0,0 +1,127 @@
1
+ # pi-user-agents
2
+
3
+ > You deserve your own agents, too. It's only fair.
4
+
5
+ Pi extensions give your agent subagents. This one gives **you** — the user — your own: background agents you dispatch mid-turn, watch live, steer, and whose results stay **out of the main agent's context** until you decide otherwise.
6
+
7
+ The moment it's for: your main agent is deep in a tool-calling frenzy on phase 1, and you already know what phase 2 needs.
8
+
9
+ ```
10
+ /agent plan the next phase and write it to phase-2.md
11
+ ```
12
+
13
+ The instant you press Enter, a background agent forks off with a snapshot of the current conversation and gets to work — while your main agent keeps going, none the wiser.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ pi install npm:@giladbarnea/pi-user-agents
19
+ ```
20
+
21
+ ## The loop
22
+
23
+ ### 1. Dispatch
24
+
25
+ `/agent <task>` starts a background agent with the current conversation as its starting context — it knows everything the main agent knows up to that moment. `/agent -i <task>` starts one with a blank slate instead. Dispatch as many as you like; they run concurrently.
26
+
27
+ Classic dispatches:
28
+
29
+ ```
30
+ /agent write a handoff document for the work currently in progress
31
+ /agent -i -m flash summarize what PARSER_SPEC.md guarantees
32
+ /agent -j --thinking high find the root cause of the flaky widget test
33
+ ```
34
+
35
+ The first preserves the session's knowledge before the context window fills, without interrupting the main agent to do it. The second is a cheap, isolated errand. The third auto-joins: its findings are delivered into the main conversation when it finishes.
36
+
37
+ ### 2. Watch
38
+
39
+ Every agent gets a row in a widget under the editor: status, task, live activity, and a one-cell context meter.
40
+
41
+ ```text
42
+ ✓ User agents
43
+ ↑↓ select · Enter view · x dispose · Esc back
44
+ └─ ⏺ ✓ /agent ▇ i am testing the widget now - read a ton...
45
+ ⎿ Loaded several large Pi source and documentation...
46
+ ```
47
+
48
+ Press `←` or `↓` from the editor to focus the widget, pick an agent, press `Enter`, and its overlay opens: the full conversation, streaming live, following the tail as it grows. Scroll up to pause following; `End` resumes it. Tool calls render as proper views — `read`, `edit`, `write`, `grep`, `find`, `ls`, and `bash` each get a dedicated format, everything else a readable generic one.
49
+
50
+ The context meter is the agent's own footer gauge in one cell: it fills `▁▂▃▄▅▆▇█` against that agent's model context window and shifts color through the same stages as Pi's footer (dim, then muted at 40%, warning at 65%, error at 85%).
51
+
52
+ ### 3. Steer
53
+
54
+ A background agent is not fire-and-forget — it's a session you can talk to.
55
+
56
+ Press `Enter` in the overlay to open the steer composer. Mid-turn, your message queues in after the current tool batch, before the next model call — exactly like steering the main agent. After the turn completes, the same composer starts **another turn** on the same live session. Follow up as many times as you need; every completed turn posts its own result card.
57
+
58
+ `Ctrl+x` interrupts only the current turn — the agent goes idle and stays available for steering, joining, or disposal. In fact, nothing disposes an agent except you: pressing `x` twice, joining it, or ending the Pi session.
59
+
60
+ ### 4. Join — or don't
61
+
62
+ This is what makes user agents different from subagents: **by default, the main agent never learns any of this happened.**
63
+
64
+ Each completed turn posts a result card into your transcript — metadata, a collapsed preview, expandable to the full Markdown response. The card is TUI-only: you read it, expand it, copy it, and the main agent's context is untouched. Ask an agent ten questions and your main agent's token budget doesn't move.
65
+
66
+ When a result does belong in the main conversation, join it:
67
+
68
+ - Dispatch with `-j`/`--join` and the result is delivered automatically on completion.
69
+ - Or press `j` in the overlay of any completed agent, whenever you decide it earned its place.
70
+
71
+ Joining delivers a compact record, not a transcript dump: every message you sent the agent and the final answer to each — no thinking, no tool traffic. The record is rebuilt from the agent's full history at join time, so early turns survive even after the agent compacts its own context. If the main agent is mid-turn, the record is steered in; if idle, it triggers a turn. A joined agent retires; its overlay stays readable.
72
+
73
+ ## Per-agent configuration
74
+
75
+ Each dispatch takes its own configuration, using the same flags as the `pi` CLI:
76
+
77
+ ```
78
+ /agent -m opus --thinking high design the caching layer
79
+ /agent --tools read,grep,find audit the error handling in src/
80
+ /agent -i --system-prompt "be terse" what does the session-format doc guarantee?
81
+ ```
82
+
83
+ Options come first; everything after them is your task, verbatim. The full grammar lives in [PARSER_SPEC.md](PARSER_SPEC.md) — not that you'll need it.
84
+
85
+ ## The fanciest autocomplete in the Pi universe
86
+
87
+ You'll rarely type any of this by hand. The moment you type `-`, a completion menu opens — no Tab needed. Anywhere the set of valid values is finite, the editor hands it to you: models, providers, thinking levels, the session's tools, even skill and prompt-template paths. You pick from what actually exists instead of typing and hoping.
88
+
89
+ And what you do type by hand is checked live, as you type. Valid options and values light up in your theme's syntax colors; anything that won't parse — a blocked option, a model that doesn't resolve, an option stranded after the task began — shows in the error color before you ever press Enter. A `/agent` line that looks right is right.
90
+
91
+ ## Reference
92
+
93
+ Everything goes through one command: `/agent [options] <task>`.
94
+
95
+ | Flag | Effect |
96
+ |---|---|
97
+ | `-i`, `--isolate` | Start without the conversation snapshot |
98
+ | `-j`, `--join` | Deliver the result into the main context on completion |
99
+ | `-m MODEL` | Model for this agent (alias of Pi's `--model`) |
100
+ | *pi CLI options* | Forwarded to the agent — `--thinking`, `--tools`, `--system-prompt`, … |
101
+
102
+ | Where | Key | Action |
103
+ |---|---|---|
104
+ | Editor | `←` / `↓` | Focus the agents widget |
105
+ | Widget | `↑` `↓` · `Enter` | Select an agent · open its overlay |
106
+ | Widget / overlay | `Ctrl+x` | Interrupt the current turn (agent stays alive) |
107
+ | Widget / overlay | `x` `x` | Dispose the agent (twice to confirm) |
108
+ | Widget / overlay | `Esc` | Back |
109
+ | Overlay | `Enter` | Steer mid-turn, or start another turn when idle |
110
+ | Overlay | `j` | Join the conversation into the main context |
111
+ | Overlay | `c` | Copy the latest response |
112
+ | Overlay | scroll · `End` | Pause tail-following · resume it |
113
+
114
+ ## Good to know
115
+
116
+ - Result cards persist across session reloads; live agent sessions don't. After a reload you keep every card, not the steerable sessions.
117
+ - Some accepted `pi` options have no effect on a background run (the session, approval, offline, API-key, and print families). They parse; they just don't do anything yet.
118
+ - `c` copies via `pbcopy`, so it's macOS-only for now.
119
+ - The overlay caps very large tool outputs and omits thinking entries.
120
+
121
+ ## Under the hood
122
+
123
+ Design notes — result delivery through the Pi SDK, the joined-message format, runtime sharing, model resolution, and editor internals — live in [INTERNALS.md](INTERNALS.md). The complete command grammar lives in [PARSER_SPEC.md](PARSER_SPEC.md).
124
+
125
+ ---
126
+
127
+ Heavily inspired by [tintinweb/pi-subagents](https://github.com/tintinweb/pi-subagents).