1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
# panto CLI/TUI: gaps and proposals
Survey of the current CLI/TUI surface (2026-07-03) and what to do about it.
Framing first, because it decides everything below: panto's job is not to
do everything, it's to make everything possible. It is a minimal
interactive shell over libpanto plus a powerful Lua extension API, in the
spirit of pi (https://pi.dev) — users build their own ideal agent on top,
and that agent need not be a coding agent at all (the shipped coding tools
are themselves deny-able Lua extensions). So every "gap" lands in one of
three buckets:
- **Core** — makes the shell itself usable and scriptable. Baseline,
not coding-specific. Ships in Zig (and should generally also be
reachable from Lua).
- **Extension API** — core shouldn't do the thing; core should make the
thing *buildable*. New API surfaces are co-designed with Travis before
building.
- **Not ours** — coding-agent conveniences. Belongs in `../panto-agent`
or other out-of-tree extensions; listed only so we stop re-proposing it.
Priorities within a bucket: **P0** = bug or table-stakes, **P1** = next
tranche, **P2** = deliberate deferral.
---
## 1. Bugs (core, P0, all small)
Fix regardless of anything else below.
1. **Duplicate short session IDs.** `panto sessions` shows `id[0..8]`
(`src/subcommand.zig:374`), but IDs are UUIDv7 and the first 8 hex chars
are the top 32 bits of the millisecond timestamp — no randomness until
char 15. Two sessions created within ~65 s in one project display
identical short IDs. Fix: shortest-unambiguous prefix per listing
(extend past 8 while any two collide).
2. **`AmbiguousSessionId` exits silently.** `--resume <prefix>` matching
two files propagates the error to a bare `exit(1)` at
`src/main.zig:282-283` — no message. Only `SessionNotFound` gets a tidy
error (`main.zig:724-727`). Fix: print the candidates and exit.
3. **`--resume` (bare) and `panto sessions` disagree on "most recent".**
`findMostRecentSession` takes the lexicographic filename max =
newest-*created* (`file_system_jsonl_store.zig:981-1010`); the listing
sorts by newest-*modified*. Fix: make `latest()` use `modified`.
4. **`trimCreated` doesn't trim.** Promises `YYYY-MM-DD HH:MM`, returns
`iso[0..16]` with the `T` intact (`src/subcommand.zig:386-394`).
5. **`--resume` flag peeking can't rewind.** A `-`-leading token after
`--resume` is warned-and-dropped instead of parsed as its own flag
(`main.zig:676-688`). Becomes real once a second agent flag exists (§2).
6. **Base config template comment says `[tools]`** where the code reads
`[extensions]` (`luarocks_runtime.zig:460` vs `config_file.zig:201`).
---
## 2. Core: command-line flags
Today the entire agent-mode flag surface is `--resume [<id>]`
(`main.zig:658-697`). For an "interactive agent SDK" the bar is: launch it,
point it at a model, script it, resume it. That's about five flags.
### P0
- **`-p, --print <prompt>` — one-shot non-interactive mode.** One agent
turn (tools and all), text to stdout, meaningful exit code; prompt from
the arg or stdin when piped. panto currently *requires* a tty
(`main.zig:494-503`) and cannot be scripted at all — the biggest gap in
the product. Composes with `--resume` for headless continuation. Pairs
with libpanto's "one-shot simple API" todo (`docs/todos.md:12`).
- **`-m, --model <provider:alias>`.** Already anticipated:
`selectModel(model_override)` exists and `main.zig:227` passes `null`.
Wire it through; accept a bare alias when unambiguous.
- **`--version`.** Embed at build time; print `panto <ver>`.
- **Unknown flags error, don't warn-and-continue** (`main.zig:686,693`).
A typo silently launching a TUI is how scripts hang.
### P1
- **`-c, --continue`** — ergonomic alias for bare `--resume`.
- **`--no-extensions`** — skip the Lua runtime: fast `-p` in CI, and the
escape hatch when an extension breaks startup. Given that even the
shipped tools are extensions, define semantics as "built-ins only,
policy-denied everything else".
- **`--effort <level>`** — same pattern as `--model`.
### Skip
- `--config <path>` — the 4-layer TOML merge already covers it.
- `--output-format json` for `-p` — wait for a real consumer.
- `--cwd` — `cd && panto`.
### `panto sessions` output (P0)
Currently `<8-char-id> <created-with-T> <N> messages`, while the useful
fields (`last_user_message`, `model`, `modified`) are computed by
`buildFileInfo` and discarded. Proposal:
```
ID MODIFIED MSGS MODEL LAST MESSAGE
0197c2a4 2026-07-01 14:32 18 anthropic:sonnet fix the segfault in tui_engine when…
0197b1f0 2026-06-30 09:15 4 openai:gpt add a --version flag
```
Sort and *show* by modified; shortest-unique IDs (§1.1); truncate last
message to width; plain aligned columns, no box-drawing. Print the sessions
dir path as the last line so the storage is discoverable. Keep resume as a
flag (it composes with `-p`/`-m`; `sessions` stays list-only) — the real
contract is that listed IDs paste into `--resume` without ambiguity.
---
## 3. Core: slash commands
Current inventory: **`/compact`** (`src/compaction.zig:13`). The framework
(registry, Lua `panto.ext.register_command`, capture-to-transcript, error
rendering) is complete — and mostly-empty is correct for panto. But a shell
whose product *is* its extension points still has to be self-describing and
steerable. These are baseline and coding-agnostic, so they're core Zig;
each should also be achievable from Lua via `panto.ext.agent` (the
sufficiency audit in §7).
### P0
- **`/help`** — registered commands (`Registry.list()` exists solely for
this and tab completion) plus the key bindings. Today nothing in the
product reveals that `/compact` or Ctrl+G exist. For an
extensions-first tool, discoverability of what's currently loaded is not
a convenience — it's how users see their own extensions.
- **`/quit`** — trivial; the only exit today is Ctrl+C/D.
- **`/model [provider:alias]`**, **`/reasoning [level]`** — no arg opens
the existing selector; with arg sets directly. Makes the selectors
discoverable and terminal-agnostic (see the Ctrl+M problem, §4).
- **`/new`** — fresh session in place; today you quit and relaunch.
### P1
- **`/resume [<id>]`** — in-TUI session switcher; no arg opens a fuzzy
`Selector` over sessions (short-id + modified + last message rows).
- **`/status`** — provider, model, reasoning, session id, message count,
context used vs window. All already in `command.Context` or computable.
### Completion and typeahead (P1)
The fuzzy machinery already exists and is good — the model selector does
case-insensitive subsequence filtering with Ctrl+N/P + Enter. Extend the
same feel to the input box (`docs/todos.md:22` tracks this):
- **Leading-`/` typeahead**: when the buffer starts with `/`, surface a
live-filtered command list (from `Registry.list()`, which exists for
exactly this) in the same selector idiom; Tab or Enter-on-selection
completes. Registered Lua commands appear automatically — this is
another discoverability channel for extensions, like `/help`.
- **Tab completes file/dir names from cwd** anywhere in the buffer:
complete the token under the cursor against the filesystem, common-prefix
first, selector on ambiguity. Coding-agnostic (paths are universal).
- Later: **per-command argument completion** declared by the command
(e.g. `/model` completing `provider:alias`, Lua commands supplying a
completion function). That's an extension API surface — §7.
---
## 4. Core: key bindings
Input box editing is solid (word motion, Ctrl+A/E/U/W, Shift+Enter
multiline, bracketed paste). Gaps:
### P0
- **Input history (Up/Down recall).** Decoded and dropped today
(`tui_components.zig:1207`). Up at buffer-top / Down at buffer-bottom
navigate history so multiline arrow-editing still works; in-memory
per-run is enough to start.
- **Idle Escape clears the input buffer.** Makes Escape uniformly "cancel
the thing in progress" (it already interrupts turns and closes
selectors).
- **Fix Ctrl+M.** Ctrl+M *is* carriage return; the model-selector binding
only exists on Kitty-protocol terminals and silently vanishes on plain
xterm/tmux. With `/model` (§3) as the discoverable path, move the chord
to a legacy-safe key (Ctrl+T is free) or accept slash-only on legacy
terminals — either way `/help` documents reality.
### P1
- **Ctrl+L** — clear/redraw (engine already has full-redraw machinery).
- **Buffer typed input during a turn** and restore it to the input box
after; full mid-turn steering waits on libpanto queueing
(`docs/todos.md:15`).
- **Dedup the chord dispatch**: Ctrl+O/M/R are re-implemented in both
`handleBytes` (`tui_app.zig:1801-1842`) and `pumpTurnKeys`
(`tui_app.zig:2286-2304`). One `appChord(key) -> ?Action` both call.
This is also the seam the keybinding extension API (§7) slots into.
### Deliberate non-features
- Kill-ring/yank, undo — already deferred in-code ("plan P2"); agreed.
- In-app transcript scrolling — engine delegates to native terminal
scrollback by design (`tui_engine.zig:36-42`); keep.
---
## 5. Core: config.toml settings
The `[defaults]`/`[compaction]`/`[extensions]`/`[providers]`/`[auth]`
sections cover the agent; the TUI itself has zero settings. A halfway
decent agent TUI supports at least:
### P1
- **`[defaults] reasoning = "<level>"`** — today reasoning comes only from
models.toml per-alias or the Ctrl+R selector; a session default belongs
next to `defaults.model`.
- **`[tui] editor = "..."`** — Ctrl+G honors only `$EDITOR`; a config
override is the conventional courtesy.
- **`[tui] tools_collapsed = true|false`** — the Ctrl+O collapse state's
starting value.
- **`[tui] theme`** — the palette is deliberately comptime-fixed today
(`tui_theme.zig:5-6` says centralization, not theming). A minimal
version: named color overrides for the existing `StyleName` slots.
P1 only if cheap; a full theming system is P2 at best.
### Skip
- Keymap section — subsumed by the keybinding extension API (§7).
- Footer layout config — subsumed by the footer segment API (§7).
- Anything per-command/per-tool — that's extension config, which
extensions can already read themselves (Lua reads files).
---
## 6. Core: the footer
Today the footer is one dim line: `provider:alias (reasoning)`, latest
context tokens, session tokens, session cost (`tui_components.zig:1533`,
all pushed from Zig via `setModel`/`setContextTokens`/...). Worth adding:
### P1
- **Context as a fraction, not a raw count.** `12.3k ctx` is meaningless
without the window; models.toml already has `context_window`. Show
`12.3k/200k ctx` or a percentage — this is the number that tells you
when compaction looms, arguably the footer's whole job.
- **Session short-id** — pairs with `/status` and `panto sessions`; makes
"which session am I in" free.
- **Transient key-hint slot** — e.g. `esc interrupt` while a turn runs.
One hint at a time, context-dependent; not a permanent hint bar.
### P2 / skip
- git branch, cwd — coding/shell-flavored and knowable from the terminal;
an extension can add these once the segment API (§7) exists. Skip in
core.
---
## 7. Extension API gaps (make it possible)
The survey surfaced these as missing Lua surfaces. Per AGENTS.md, each gets
co-designed before building — this list is scope, not spec.
- **Keybinding registration.** Wanted: extensions can bind chords to Lua
handlers (and presumably rebind/shadow core ones). Blocked on the
`appChord` central dispatch (§4) existing first. Design questions for
the co-design pass: conflict policy vs core chords, per-context bindings
(idle vs during-turn vs selector), and whether declared bindings
auto-appear in `/help`.
- **Footer segments.** The footer is entirely Zig-fed today; extensions
have no way to add a readout (git branch, active skill, queue depth).
Likely shape: register a segment that returns a short string, repainted
on demand — rhymes with the existing screen-component API.
- **Completion providers.** Per-command argument completion for Lua slash
commands (§3): a command optionally supplies a completion function.
- **Session store access** from Lua (list/resolve/load). Needed for an
extension to build its own `/resume`-like or archival behaviors; today
sessions are invisible to extensions.
- **Selector/prompt UI for extensions.** TUI screen components are done
(`docs/todos.md:31`); a fuzzy-pick-from-list primitive is the missing
interactive piece extensions keep needing (model pickers, skill pickers,
session pickers all rhyme).
Not a gap (previously misreported here): the current agent and
conversation **are** exposed — `panto.ext.agent` wraps the live session
agent with the full method surface (`lua_runtime.zig:219-242`), and the
conversation hangs off it. What's worth auditing instead is whether that
surface is *sufficient* for §3-parity from Lua: model/config switching,
starting a fresh session, reading usage/config for a `/status`-alike.
---
## 8. Not ours (so we stop re-proposing it)
- **`/usage`, `/cost`** — buildable today-ish from the Lua usage-metrics
API + models.toml pricing; panto-agent material.
- **`/init`, `/review`, `/pr`, rules/skills injection** — coding-agent
features; panto-agent already owns this space (`agent.rules`,
`agent.skill`).
- **`/undo` / checkpointing** — real design work and coding-flavored;
out-of-tree when someone wants it.
- **User keymap config file** — subsumed by the keybinding extension API
(§7); a TOML keymap section can be revisited if non-extension users ask.
---
## 9. Sequencing
1. §1 bug fixes + `panto sessions` output + shortest-unique IDs — one PR,
pure fixes.
2. `--model`, `--version`, strict flag parsing — small PR.
3. `-p/--print` — the headline; with libpanto's one-shot todo or a thin
plain-text presenter over the existing loop.
4. Slash commands P0 (`/help`, `/quit`, `/model`, `/reasoning`, `/new`).
5. Keybindings P0 (history, idle-Escape, Ctrl+M move) + `appChord` dedup.
6. Footer P1 (context fraction, session id) + config.toml `[defaults]
reasoning` / `[tui]` basics.
7. `/resume` switcher, `/status`, completion/typeahead (§3).
8. Extension API co-design round: keybindings, footer segments, completion
providers, session store, selector primitive (§7) — then panto-agent
picks up §8.
|