Documentation
¶
Overview ¶
Package dispatch is the runtime's production tool-dispatch concrete — the one full `steering.ToolExecutor` implementation (Phase 110a, D-194; originally Phase 83i, D-152, as the dev binary's executor).
Before 83i the runloop's default case dropped every planner CallTool decision on the floor (Phase 53's deliberately-punted scope), which made multi-step ReAct structurally broken against real LLMs because the planner never saw tool observations. The audit pinned this as the root cause of the "64 steps, 0 tools called" failure mode that surfaced in the v1.1 operator validation. Phase 110a promoted the executor out of `package main` (the SDK friction audit's Pattern 1 / P1 finding) so every assembly — `cmd/harbor`, `harbortest/devstack`, a headless embedder's own run loop — wires the SAME dispatch semantics via NewToolExecutor.
The executor closes the planner→runtime seam against the production `tools.ToolCatalog`:
CallTool: look up the descriptor by name, call Invoke under the per-step ctx, return the typed ToolResult.Value (plus Meta) as the observation. The planner's next step sees this on `RunContext.Trajectory.Steps[N].Observation`.
CallParallel: fan the branches out concurrently via `internal/runtime/parallel` in non-atomic mode (Phase 107d — D-169).
SpawnTask / AwaitTask: drive the background-task registry with spawn-depth caps and terminal-status polling (Phase 107e — D-170).
The executor is constructed once per stack and shared across every run; it holds only the catalog + artifact store + task registry (immutable after construction) + a logger, so the D-025 reuse contract is satisfied.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func HeavyTruncationSummary ¶
HeavyTruncationSummary builds the small llmObservation the planner renders for a heavy tool result. For JSON-object results it emits a field-aware preview that preserves every top-level scalar / small field verbatim and replaces oversized nested values with a `[omitted: N bytes]` sentinel — so the model sees both what's available and what was pruned. For non-object results (arrays, scalars at top level) it falls back to byte-truncation at `previewTotalMaxBytes`.
Carries: the tool name, byte size of the full result, the preview, the `truncated: true` signal, and the artifact ID when available.
This exported identifier IS the heavy-truncation shape contract: the React planner's prompt renderer (`internal/planner/react/prompt.go::renderHeavyContentMap`) pattern-matches the `{tool, size_bytes, truncated, preview, artifact_ref}` map this function emits. Changing a key here is a prompt-renderer change in the same PR (Phase 110a re-pointed the renderer's citation from the pre-promotion `cmd/harbor` copy to this function).
func NewToolExecutor ¶
func NewToolExecutor(cat tools.ToolCatalog, store artifacts.ArtifactStore, taskReg tasks.TaskRegistry, opts ...Option) steering.ToolExecutor
NewToolExecutor binds the catalog + artifact store + task registry the run loop dispatches against — the SAME instances the stack's boot wiring constructs. The returned executor covers all four non-Finish Decision shapes (CallTool, CallParallel, SpawnTask, AwaitTask) with the D-026 heavy-result→artifact promotion applied to every observation that reaches the planner / LLM edge.
`taskReg` MAY be nil in degraded wiring — SpawnTask / AwaitTask then fail loud with `steering.ErrDecisionShapeUnsupported` rather than panic. `store` MAY be nil — heavy results then degrade to a loudly logged truncated preview instead of an artifact promotion.
Concurrent reuse (D-025): the executor is a compiled artifact — construct once per stack, share across N concurrent runs. Per-run state lives in ctx + RunContext, never on the executor.
Types ¶
type Option ¶
type Option func(*toolExecutor)
Option configures the executor NewToolExecutor constructs.
func WithHeavyThreshold ¶
WithHeavyThreshold sets the D-026 heavy-output threshold in bytes: tool results whose JSON encoding meets or exceeds it get promoted to artifact-backed truncation summaries before reaching the planner / LLM edge. Non-positive values fall back to the 32 KiB safety floor (the Wave 11 default) — the same normalization the pre-110a constructor applied, so passing an unset config value through is safe.
func WithLogger ¶
WithLogger sets the executor's logger. Nil falls back to slog.Default().
func WithMaxSpawnDepth ¶
WithMaxSpawnDepth caps the ParentTaskID-chain depth of planner-spawned background tasks (planner.absolute_max_spawn_depth). Non-positive values fall back to `config.DefaultSpawnDepthCap` (the one source of the spawn-depth default — D-196).