dispatch

package
v1.20.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: Apache-2.0 Imports: 17 Imported by: 0

Documentation

Overview

Package dispatch is the runtime's production tool-dispatch concrete — the one full `steering.ToolExecutor` implementation ( originally, as the dev binary's executor).

Previously the runloop's default case dropped every planner CallTool decision on the floor (the 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. An earlier phase 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.

  • SpawnTask / AwaitTask: drive the background-task registry with spawn-depth caps and terminal-status polling.

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 reuse contract is satisfied.

Index

Constants

This section is empty.

Variables

View Source
var ErrTaskNotOwnDescendant = errors.New("dispatch: task is not a descendant this run spawned")

ErrTaskNotOwnDescendant is returned when a task observation/cancel meta-tool (_task_status / _cancel_task) names a target the calling run did not spawn — a task whose ParentTaskID chain does not reach the run's own task. It is a SCOPE (authorization) violation, distinct from a not-found or identity-secrecy boundary: the caller's own session may already know the sibling task exists (it is Console-visible), so the message says "not your descendant" so the model can self-correct rather than retry a typo. This is IN ADDITION to the registry's own (tenant, user, session) identity-visibility check, never instead of it.

Functions

func HeavyTruncationSummary

func HeavyTruncationSummary(tool string, raw any, encoded []byte, size int, artifactID string) any

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 (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 every non-Finish Decision shape (CallTool, CallParallel, SpawnTask, AwaitTask, and the heterogeneous Batch) with the 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: 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

func WithHeavyThreshold(bytes int) Option

WithHeavyThreshold sets the 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 default) — the same normalization the pre-110a constructor applied, so passing an unset config value through is safe.

func WithLogger

func WithLogger(l *slog.Logger) Option

WithLogger sets the executor's logger. Nil falls back to slog.Default().

func WithMaxBatchSpawns added in v1.16.0

func WithMaxBatchSpawns(n int) Option

WithMaxBatchSpawns caps a Batch decision's Spawns length (planner.max_batch_spawns). Non-positive values fall back to `config.DefaultMaxBatchSpawns` (the one source of the batch-spawn breadth default). Exceeding the cap rejects the WHOLE batch before any tool branch dispatches or any spawn registers — never a silent truncation to the first N spawns.

func WithMaxSpawnDepth

func WithMaxSpawnDepth(n int) Option

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).

func WithSteeringRegistry added in v1.17.0

func WithSteeringRegistry(reg *steering.Registry) Option

WithSteeringRegistry binds the process-wide steering inbox registry the planner-facing steer/pause/resume verbs route through. It is the SAME registry the operator's steering control surface uses — the agent's verbs and the operator's verbs converge on one per-sub-run inbox and the one unified pause/resume primitive, never a second path. Nil (the default) leaves SteerTask / PauseTask / ResumeTask failing loud with steering.ErrDecisionShapeUnsupported.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL