Skip to content

Plugins Phase 5: agent-facing hooks and context injection #9

Open

Description

@ashx-j

Part of the plugin system epic #3. Depends on #7 and #8. Phase 5 lets plugins shape what agents see and do inside a turn.

  • Turn lifecycle hooks. (2026-10-31, feat/plugins-server-hooks: turns.onRuntimeRequest answers approvals and questions, and send hooks can replace or block a message.) Partly built in feat(plugins): run-completed hook, richer send hook and agent plugin tools #21: beforeDispatch gets the message id, attachment summaries and the requested model; turns.afterRunCompleted delivers every ended run with a stored cursor (one per registration, in order). onRuntimeRequest is not built: answering approvals needs per-plugin rules the user sets. Original text: turns.beforeDispatch (from Phase 3) gains the full message with attachments and the thread's model selection; add turns.afterRunCompleted(run) (reacts to run completion events with the run's items available for reading), turns.onRuntimeRequest(request) (approval or question raised by the provider; plugin may auto-answer under rules the user configured in its settings). Each hook is an event reaction over streamStoredEventsFrom({ afterSequence, eventType }) with the plugin's acknowledged sequence stored in PluginStorage, so a plugin that was off when the run finished catches up on start. streamDomainEvents starts at the latest sequence and cannot be used for catch-up. Shape modelled on saphid's feat(server): deliver finished runs to plugins from an acknowledged cursor pingdotgg/t3code#16048.
  • Context injection. (2026-10-31: send hooks append or replace text, and web plugins add @ items, shown as chips since 2026-11-01, see Plugins Phase 4: web plugin runtime and UI contribution registries #8.) A plugin can register context.providers that contribute text blocks to the outgoing turn, each shown as a composer chip on web so the user sees what was added and can remove it for that message. Server records which plugin added what in the message metadata.
  • Skills and instructions. (2026-10-31, feat/plugins-server-hooks: a skills/ folder and agents.addInstructions reach Claude and Codex. ACP agents, OpenCode, Cursor, Pi and Muse take neither from T3. 2026-11-01, feat/plugins-server-gaps: the $ picker lists plugin skills for Claude and Codex as <plugin>: <skill>.) A plugin directory may contain skills/ in the same format the composer's $ skill picker already understands (ServerProviderSkill). Plugin skills appear in the picker labelled with the plugin name, for every provider that supports skill injection.
  • Slash commands with server handlers. (2026-10-31, feat/plugins-server-hooks: slashCommands.register lists the command for every client through the server-supplied list and runs the handler at dispatch, which returns text to send or a block. 2026-11-01, feat/plugins-server-gaps, with Ash's approval for one Orchestrator.ts command: a handler can return { reply }, recorded with the command as an already finished run, so no agent turn starts and every client shows it unchanged.) Delivered through the existing server-supplied provider-slash-command menu variant (see Phase 4b). A plugin slash command may rewrite the message, dispatch a different message, or answer directly without a model call (rendered through the plg_ dynamic tool renderer from Phase 4c).
  • MCP tool policy. (Done in Phase 3.) Plugins can mark their tools as read or write (Phase 3) and the thread's runtime mode gates write tools the same way built-ins are gated.
  • Agent-facing management. (feat(plugins): run-completed hook, richer send hook and agent plugin tools #21; both tools are writes and appear only once a plugin is installed.) MCP tools t3_plugins_invoke (call a plugin RPC by name, write access) so an agent can drive a plugin, and t3_plugins_set_enabled guarded as a write.

Measurements

message.dispatch latency with zero hooks registered: unchanged from Phase 3. With one no-op hook: report the added milliseconds.

Tests

A beforeDispatch hook that appends a context block shows up in the persisted message and in the provider input (use the Replay dev provider). afterRunCompleted fires once per run and catches up after a restart from the stored cursor. An onRuntimeRequest hook that answers an approval results in the approval event. A plugin skill appears in the skill list for Claude.

Done when

Draft PR feat(plugins): turn lifecycle hooks, context injection and plugin skills on the fork.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    pluginsPlugin system roadmap

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions