parallel

package
v1.3.1 Latest Latest
Warning

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

Go to latest
Published: Jun 11, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package parallel ships Harbor's runtime parallel-call executor — the consumer of planner.CallParallel Decisions (RFC §6.2, Phase 47, D-056). The Planner emits CallParallel; this package dispatches the branches concurrently with three settled invariants:

  1. **Atomic setup validation.** Every branch's args is validated via the resolved tools.ToolDescriptor.Validate BEFORE any branch dispatches. A single invalid-args branch fails the whole call with wrapped planner.ErrParallelBranchInvalidArgs — no branch has executed; no side-effect tool has fired; the planner's next step sees a clean failure observation.

  2. **System cap on branch count.** Any CallParallel with `len(Branches) > planner.AbsoluteMaxParallel` (=50) fails the whole call with planner.ErrParallelCapExceeded — defence in depth against a runaway emission.

  3. **Parallel-pause atomicity.** No branch starts side-effecting tools, or all reach checkpointed observation before pause commits (RFC §6.2). The unified pause/resume primitive lands at Phase 50; for Phase 47 a mid-execution pause request fails loud with planner.ErrParallelPauseUnsupported. Phase 50 upgrades this path to a checkpointed atomic pause.

Three join shapes are supported (D-056):

  • planner.JoinAll: wait for every branch to terminate; return the result slice in branch-index order. Default.
  • planner.JoinFirstSuccess: return the first successful branch's result; cancel the remainder mid-flight. Failures do NOT cancel until all remaining branches terminate.
  • planner.JoinN: wait until N branches succeed, then cancel the remainder. JoinSpec.N carries the threshold; 0 < N ≤ len(Branches) is validated at setup time.

**Import-graph contract (§13).** This package lives in `internal/runtime/parallel`, OUTSIDE the planner subtree, so it MAY import `internal/planner`. The reverse (planner → runtime) is FORBIDDEN by the Phase 42 conformance lint; the executor is the one-way dispatch site that consumes the typed planner shape.

**Identity (§6 rule 9).** Every dispatch reads the run's identity quadruple from ctx; missing identity returns wrapped tools.ErrIdentityRequired. Branches inherit the parent ctx so identity propagates unchanged to every tool invocation.

**Concurrent reuse (D-025).** The Executor struct is immutable after construction; per-call state lives on the stack and in ctx. `concurrent_test.go` pins N≥128 concurrent invocations against one shared instance under `-race`.

**Deterministic merge keys.** Each Result entry carries the branch's input index AND its tool name. JoinAll returns results in branch-index order; JoinFirstSuccess returns a single-entry slice keyed on the successful branch's index; JoinN returns the first N successful branches in completion order (the per-branch index + tool name is the deterministic key for downstream observation rendering).

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ExecuteOption added in v1.2.0

type ExecuteOption func(*executeOptions)

ExecuteOption configures a single Executor.Execute call. Options are per-call (NOT stored on the immutable Executor — D-025), so a shared executor instance can serve atomic programmatic callers and non-atomic native callers concurrently without cross-talk.

func WithNonAtomicSetup added in v1.2.0

func WithNonAtomicSetup() ExecuteOption

WithNonAtomicSetup selects non-atomic setup validation (Phase 107d — D-169). In non-atomic mode a branch whose tool fails to resolve, or whose args fail the descriptor's Validate, surfaces as that branch's Result.Err and is skipped at dispatch; the remaining valid branches still fan out. The result slice carries exactly one Result per branch, so every provider `tool_call_id` can be answered — the wire-contract requirement the native React path depends on.

The atomic default (a single invalid-args branch fails the whole call with planner.ErrParallelBranchInvalidArgs before ANY branch dispatches) is unchanged for existing callers. The branch-count cap (planner.AbsoluteMaxParallel) and the missing-identity reject (identity.ErrIdentityMissing) stay fail-loud whole-call aborts in BOTH modes — non-atomic relaxes only per-branch setup, never the system-level guards.

Non-atomic setup is honoured only on the planner.JoinAll path (the native React surface); the other join kinds are programmatic-planner surface and keep the atomic posture.

type Executor

type Executor struct {
	// contains filtered or unexported fields
}

Executor dispatches planner.CallParallel Decisions. Constructed once; safe for N concurrent invocations against a shared instance (D-025).

The executor is intentionally minimal — it does NOT apply tools.ToolPolicy retry / timeout (that is the per-tool dispatch shell's job; the executor just invokes the descriptor's Invoke once per branch). Future runtime phases that wire the per-tool policy shell at the dispatcher should compose the shell INSIDE the per-branch goroutine.

func New

func New(resolver Resolver) *Executor

New constructs an Executor backed by the supplied Resolver. Nil resolver panics — composition error caught at boot.

func (*Executor) Execute

func (e *Executor) Execute(ctx context.Context, call planner.CallParallel, opts ...ExecuteOption) ([]Result, error)

Execute dispatches the planner.CallParallel Decision per the JoinSpec semantics and returns the per-branch results in deterministic order.

Step 1 (atomic setup validation):

If any setup step fails, NO branch dispatches. The error is the only return; results is nil. This is the load-bearing "atomicity-contract" surface RFC §6.2 names.

Step 2 (dispatch):

Each surviving branch fires in its own goroutine with the supplied ctx (cancellation, identity, deadline propagate). Per the JoinSpec:

  • JoinAll: wait for every branch; return all results in branch-index order.
  • JoinFirstSuccess: return the first successful branch; cancel the remainder via a derived ctx; the returned slice is single-entry. If every branch fails, the slice is empty + the error is a joined error of every branch's failure.
  • JoinN: wait until N branches succeed; cancel the remainder. The returned slice carries the N successes in COMPLETION order (each Result still carries its original branch Index for deterministic merge-key consumption downstream).

Returns (results, error). The error path is reserved for setup-validation failures and JoinFirstSuccess/JoinN exhaustion (no branch met the threshold). Per-branch failures land on Result.Err — the caller (planner step adapter) decides how to surface mixed-success-and-failure observations.

type Resolver

type Resolver interface {
	// Resolve returns the descriptor for name. found=false on miss.
	Resolve(name string) (tools.ToolDescriptor, bool)
}

Resolver is the narrow descriptor-lookup view the executor depends on. The full tools.ToolCatalog surface (Register / List) is not needed at dispatch — only Resolve. This narrowing lets tests inject a tiny stub resolver without standing up the production catalog.

type Result

type Result struct {
	// Index is the branch's position in the input [planner.CallParallel.Branches]
	// slice. Stable for the lifetime of the call.
	Index int
	// Tool is the branch's tool name. Same as Branches[Index].Tool.
	Tool string
	// Result is the [tools.ToolResult] on success. Nil on failure /
	// cancellation.
	Result *tools.ToolResult
	// Err is the upstream error on failure. Nil on success.
	Err error
}

Result is the per-branch outcome the executor produces. Each entry carries the branch's input index + tool name (the deterministic merge key), the tools.ToolResult on success, and the error on failure.

Either Result is populated (success) or Err is populated (failure) — never both. Cancelled branches surface Err = context.Canceled.

Jump to

Keyboard shortcuts

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