agent,lua: a plugin platform for supervision and scheduling plugins - #875
Open
Firaenix wants to merge 2 commits into
Open
agent,lua: a plugin platform for supervision and scheduling plugins#875Firaenix wants to merge 2 commits into
Firaenix wants to merge 2 commits into
Conversation
Plugins that need a picker rebuild one from scratch: a floating window,
hand-rolled fuzzy matching, their own key handling and section headers.
The result never quite matches the built-in pickers, and every plugin
pays the cost again. Meanwhile the real thing - the ratatui ListPicker
that /model and friends render with - already does filtering, sections,
scrolling, paste, theming and keybinds, and only lacks a way for a
plugin to feed it rows.
`maki.ui.picker(items, opts?)` opens exactly that component, populated
with plugin-supplied rows, and answers when the user decides:
- items: `{ label, detail?, suffix?, section?, highlighted?, data? }`
per row - the same axes PickerItem gives the native pickers. `data`
is an opaque JSON value echoed back on selection.
- opts: `title`, `initial` (label to preselect), and `keys` - extra
keybinds (`{ key = "R", hint = "refresh" }`) rendered in the footer.
Uppercase by convention, matching the model picker's shift-only rule,
so lowercase letters keep feeding the fuzzy filter. Pressing one
answers `{ key = "R" }` so the caller can act and reopen.
- Answers: `{ index, label, data }` on Enter (index is 1-based into
the caller's array), `{ key }` on a registered key, and
`(nil, "cancelled")` on dismissal - including when the overlay is
closed from outside, so the waiting coroutine never parks forever.
Plumbing follows the existing patterns: a `LuaPicker` component wraps
`ListPicker<Entry>` the same way ModelPicker does and joins the overlay
set; the request rides a new `UiAction::Picker` through the same
round-trip `maki.model` uses. ListPicker itself learns one small trick:
`set_footer_line`, a prebuilt footer for pickers whose hints are only
known at open time (the existing `with_footer_builder` is a plain fn
pointer and cannot capture the caller's keys).
With `maki.model.info`/`available`/`set`/`refresh`, a complete model
popup is now: list specs, map to rows with price hints, open the
picker, switch on the answer.
Firaenix
force-pushed
the
pr/plugin-platform
branch
4 times, most recently
from
August 26, 2026 15:23
cfd4417 to
d41e235
Compare
Firaenix
force-pushed
the
pr/plugin-platform
branch
2 times, most recently
from
August 27, 2026 18:30
3842913 to
bb7d7a9
Compare
Everything a plugin needs to supervise or route the agent's work without forking a builtin, in one connected change: Model catalog. Discovery persists to an on-disk cache and replays at startup; Anthropic /v1/models metadata (max_input_tokens, max_tokens) flows into ModelInfo; pricing distinguishes billed cost from list price for subscription-subsidised providers. Lua reads it all through maki.model.info(spec) and kicks re-discovery with maki.model.refresh(). Reviewer chain. maki.api.register_reviewer installs a chain link that classifies any tool call which would otherwise prompt the human: ALLOW runs it, DENY blocks it with the reason fed back to the agent, ASK escalates to the next link and finally the human. A link is either a model judging against policy text, or a plugin handler function that computes the verdict itself (rulebooks, quotas, external approval systems), receiving the decoded input, the derived permission scopes (for bash: the treesitter-parsed command segments), parseability, cwd, the last user message, and the attempt history for repeated calls. Model links fence everything as untrusted data with injection attempts called out as grounds to deny. Under yolo an unresolved chain denies with retry guidance (customisable per registration via redirect_guidance) instead of prompting, hardening to a stop instruction after three redirects in a turn, so unattended runs never stall. Model calls resolve through the normal provider stack, respect a per-link timeout, report usage into the session cost pipeline, and emit OTel tool decisions with a dedicated reviewer source. Registration is live (same-name upsert, /reload clears) and gated behind a new 'reviewers' grant in plugin.toml, since an always-allow link would silently bypass prompting. Plugins observe outcomes through the ToolReviewed autocmd, spend included; the UI folds reviewer usage into session totals and toasts denials. Task slots. The bundled task tool declares three extension slots - task.resolve_model, task.system_prompt, task.tools - so a plugin can reroute subagent model choice, steer subagent prompts, or filter their tools by wrapping a slot instead of replacing the tool.
Firaenix
force-pushed
the
pr/plugin-platform
branch
from
August 28, 2026 07:07
bb7d7a9 to
657769e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Builds on #874 (the two commits below it in the branch); review the top commit.
Everything a plugin needs to supervise or route the agent's work without forking a builtin:
Model catalog: discovery persists to an on-disk cache and replays at startup; Anthropic /v1/models metadata flows into ModelInfo; pricing distinguishes billed cost from list price for subscription-subsidised providers. Lua reads it via maki.model.info(spec) and maki.model.refresh().
Reviewer chain: maki.api.register_reviewer installs a chain link that classifies any tool call which would otherwise prompt: ALLOW runs it, DENY blocks it with the reason fed back to the agent, ASK escalates down the chain and finally to the human. A link is either a model judging against policy text or a plugin handler function that computes the verdict itself (rulebooks, quotas, external approval flows), receiving the decoded input, derived scopes (for bash: the parsed command segments), cwd, and attempt history. Under yolo an unresolved chain denies with retry guidance (customisable via redirect_guidance) instead of prompting, hardening to a stop instruction after three redirects in a turn, so unattended runs never stall on a question. Reviewers receive the raw input, derived permission scopes, cwd, last user message, and attempt history, fenced as untrusted data with injection attempts named as deny grounds. Calls resolve through the normal provider stack with per-link timeouts, report usage into session cost totals, and emit tool decisions with a dedicated reviewer OTel source. Registration is live and gated behind a new 'reviewers' grant in plugin.toml, since an always-allow reviewer would silently bypass prompting. Outcomes surface through a ToolReviewed autocmd (spend included) and native deny/redirect toasts.
Task slots: the bundled task tool declares task.resolve_model, task.system_prompt, and task.tools extension slots, so a plugin can reroute subagent model choice, steer prompts, or filter tools by wrapping a slot instead of shipping a fork of the tool.