Slash-command provider — data types#
SlashCommandSpec#
Declares one directly-invocable command this provider exposes — a tool-shaped operation invoked via this same provider's own SlashCommandService.Invoke, never by naming another provider's tool operation.
SlashCommandSpec {
name string // MUST — the command's name, without the leading "/". MUST be unique
// across every direct-invoke command declared by every provider in
// the session — a name collision at config-load time is a hard error.
description string // MUST — shown in the frontend's hotkey_hints region and wherever
// else the frontend surfaces available commands
input_schema JSONSchema // MUST — common subset per model/data-types.md#tool-schema;
// describes the shape of SlashCommandCall.arguments
kind tool.v1.ToolKind // MUST — reused verbatim, see "Reused tool.v1 types" below
risk tool.v1.RiskClass // MUST — reused verbatim
concurrency tool.v1.ConcurrencySpec // MUST, except for kind == interactive — reused verbatim
streaming bool // MUST — true if Invoke may emit intermediate events (output_chunk,
// progress, partial_result) before the terminal event; false if
// Invoke always emits exactly one terminal event with no lead-up
default_timeout Duration? // SHOULD — the deadline the kernel applies to Invoke for this
// command absent an agent.hcl override; omitted means the kernel's
// own global default applies instead
idempotent bool // MUST — true iff re-running this command with identical arguments
// cannot produce a different end state than running it once; see
// tool/conformance.md#the-idempotent--retry-interaction
// No output_schema: unlike a tool operation, a direct-invoke command is never presented to the
// model as a callable tool — it dispatches without a model turn — so there is no LLM-facing
// structured-output contract to validate its result against.
}
kind/risk/concurrency carry the identical MUST-level rules tool/protocol.md#getschema and tool/data-types.md define for ToolSchema's same-named fields — see protocol.md#getcapabilities for the full cross-reference. This is not incidental convergence: a direct-invoke command flows through the identical plan/apply gate a tool call does (pluggableharness.plan.v1.PlanItem), so it needs the identical classification vocabulary, not a parallel copy of it.
SlashCommandCall / SlashCommandEvent#
SlashCommandCall {
id string // MUST — kernel-assigned, echoed in every emitted event for correlation
name string // MUST — matches a SlashCommandSpec.name from this provider's
// GetCapabilities response
arguments JSON // MUST — already-parsed JSON conforming to that SlashCommandSpec's
// input_schema
call_context CallContext // MUST be set by the kernel — pluggableharness.common.v1.CallContext.
// Carries session_id/turn_id, echoed by the plugin on its own
// KernelCallbackService.Emit/Log calls for attribution, and
// working_directory — the session's cwd at call time.
}
SlashCommandEvent = oneof {
output_chunk { stream: tool.v1.OutputStream, data: bytes }
progress { message: string, fraction_complete: float? }
partial_result { payload: JSON } // incremental structured output, e.g. progress lines as emitted
exit_status { exit_code: int, signal: string? } // process-backed commands only
result tool.v1.ToolResult // terminal, success — reused verbatim
error tool.v1.ToolError // terminal, failure — reused verbatim, see
// conformance.md#error-taxonomy
}
SlashCommandEvent is structurally identical to tool/data-types.md#toolcall--toolevent--toolresult's ToolEvent, and the same streaming contract applies verbatim: output_chunk, progress, and partial_result MAY each appear zero or more times before the stream's terminal event; exit_status MAY appear at most once, and only for a command whose implementation is process-backed; exactly one of result/error MUST close the stream; output_chunk ordering within one stream MUST be preserved. See protocol.md#invoke for the full ordering and cancellation semantics — this document does not restate them.
On the wire, SlashCommandCall/SlashCommandEvent are wrapped in thin per-RPC envelope messages (InvokeRequest { call = 1; }, InvokeResponse { event = 1; }), the same pattern tool/data-types.md uses — see examples.md for the full message definitions.
Reused tool.v1 types#
This category declares no parallel copy of any of the following — each is the literal pluggableharness.tool.v1 message or enum, imported and reused as-is:
| Type | Reused as | Defined in |
|---|---|---|
ToolKind | SlashCommandSpec.kind | tool/protocol.md#getschema |
RiskClass | SlashCommandSpec.risk | tool/data-types.md#riskclass |
ConcurrencySpec | SlashCommandSpec.concurrency | tool/data-types.md#concurrencyspec |
ToolResult | SlashCommandEvent.result | tool/data-types.md#toolcall--toolevent--toolresult |
ToolError | SlashCommandEvent.error | tool/conformance.md#error-taxonomy |
OutputStream | SlashCommandEvent.OutputChunk.stream | tool/examples.md#the-wire-protocol |
A plugin MUST NOT redeclare any of these types under pluggableharness.slashcommand.v1; a kernel-side or plugin-side consumer decodes them exactly as it would decode the identically-named tool.v1 message elsewhere in the system. This is a deliberate consequence of kind/risk/concurrency/results/errors meaning the same thing regardless of which of the two categories produced the call — see README.md for why this category exists as a sibling to tool/ rather than folding into it.
SlashCommandSpec vs. PromptExpansionSpec#
Two deliberately distinct "slash command" concepts exist in this protocol:
SlashCommandSpec (this category) | pluggableharness.common.v1.PromptExpansionSpec | |
|---|---|---|
| Executes anything | Yes — dispatches through this provider's own Invoke, and through the plan/apply gate for TOOL_KIND_RESOURCE commands | No — purely a static template expansion |
| Declared by | A slashcommand.v1 provider, in GetCapabilitiesResponse.commands | Any of the other six categories, directly in their own capability response (e.g. tool/protocol.md#getschema's slash_commands field) |
Has input_schema/kind/risk/concurrency | Yes | No — only name, description, and a template string |
| Costs a model turn | Not inherently — a TOOL_KIND_DATA_SOURCE or TOOL_KIND_RESOURCE command executes directly; nothing about invoking it requires a model turn | Always — the kernel expands template with the user's arguments and submits the result as an ordinary user_message, which the model then turns on |
| Producer's own name-uniqueness namespace | Its own — a name collision among SlashCommandSpecs across every provider in the session is a hard error, independent of the PromptExpansionSpec namespace | Its own, independent of the SlashCommandSpec namespace above |
A tool provider (or any other category's provider) wanting a direct-invoke shortcut into one of its own operations implements SlashCommandService alongside its own category's service in the same process — hashicorp/go-plugin muxes multiple gRPC services per subprocess connection, the same mechanism agent-loop/hook-dispatch.md#wire-contract--pluggableharnesshookv1 already relies on for HookSubscriberService. See conformance.md#open-questions for the current, expected-common, unresolved shape of that combination.