dispatch

package
v1.23.0 Latest Latest
Warning

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

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 18 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 ErrArtifactRefNotFound = errors.New("dispatch: artifact reference does not resolve under this run's scope")

ErrArtifactRefNotFound is returned to a tool whose artifact-reference argument names an id that does not resolve under the dispatching run's isolation triple. It is deliberately indistinguishable from an id that exists under another triple: the store answers found-false for both, and revealing which case applied would make existence readable across a boundary the read key closes.

View Source
var ErrArtifactStoreUnavailable = errors.New("dispatch: artifact reference cannot be resolved (no artifact store wired)")

ErrArtifactStoreUnavailable is returned to a tool whose artifact-reference argument cannot be resolved because the stack wired no artifact store. The reference fails loud rather than arriving as an empty value the tool would read as an empty artifact.

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