harness

package
v0.11.0 Latest Latest
Warning

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

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 16 Imported by: 0

Documentation

Overview

Package harness provides agent-harness orchestration primitives that compose over the SDK agent loop — context-isolated and dynamic subagents, and the "deep agent" preset that assembles them with planning (WC-A-001) and the virtual filesystem (WC-A-002).

Index

Constants

View Source
const DefaultDeepAgentSystemPrompt = `` /* 874-byte string literal not displayed */

DefaultDeepAgentSystemPrompt is the opinionated system prompt used when DeepAgentConfig.SystemPrompt is empty. It teaches the model to use the harness primitives the preset wires in: the plan, the virtual filesystem, and subagent delegation — and that older turns are compacted, so durable state belongs in the plan and files, not the conversation.

View Source
const DefaultMaxSubAgentDepth = 3

DefaultMaxSubAgentDepth bounds how deeply subagents may spawn further subagents, guarding against unbounded recursion (an agent that keeps delegating to itself).

View Source
const SpawnToolName = "spawn_subagent"

SpawnToolName is the name the model uses to delegate a sub-task.

View Source
const SubAgentGraphID = "chronos.subagent"

SubAgentGraphID is the id of the shared, single-node graph that runs a registered subagent. Every worker compiles it over the SAME SubAgentService, so a subagent run enqueued on one node — or orphaned when a worker dies — can be executed by any other worker.

Variables

This section is empty.

Functions

func Attach

func Attach(svc *SubAgentService, runner Runner)

Attach registers the spawn_subagent tool on the parent agent so it can delegate sub-tasks. The service already holds the parent's tool registry, so no separate parent argument is needed (avoiding a nil-deref). Call it after building the parent, since the service is derived from the built agent:

parent, _ := agent.New(...).Build()
svc, _ := harness.NewSubAgentService(parent)
svc.Register(harness.SubAgentSpec{Name: "researcher", SystemPrompt: "..."})
harness.Attach(svc, harness.NewInProcessRunner(svc))

A builder method is intentionally not provided: sdk/agent must not import sdk/harness (harness already imports agent), so wiring lives here.

func NewDeepAgent

func NewDeepAgent(cfg DeepAgentConfig) (*agent.Agent, error)

NewDeepAgent assembles the full agent harness — planning (WC-A-001), a virtual filesystem (WC-A-002), context-isolated subagents (WC-A-003), automatic compaction with the plan pinned (WC-A-004), and semantic memory recall (WC-D-001) — into a single ready-to-run agent with a sensible default prompt and tool set. Everything is override-able through DeepAgentConfig.

Use it with a Storage backend and Agent.ChatWithSession for the full durable, self-compacting experience:

a, _ := harness.NewDeepAgent(harness.DeepAgentConfig{Model: p, Storage: store})
resp, _ := a.ChatWithSession(ctx, "task-1", "Research X and write a report.")

func NewSpawnSubAgentTool

func NewSpawnSubAgentTool(svc *SubAgentService, runner Runner) *tool.Definition

NewSpawnSubAgentTool returns the tool a parent agent calls to delegate a sub-task to a subagent. The subagent runs in a fresh, isolated conversation (via runner) and only its final result is returned to the parent — the subagent's intermediate reasoning and tool calls never enter the parent's context window. The subagent is either a pre-registered template (selected by "agent") or one described dynamically in the call.

func NewSubAgentGraph

func NewSubAgentGraph(svc *SubAgentService) (*graph.CompiledGraph, error)

NewSubAgentGraph builds the shared subagent-runner graph over svc. Wire it into a queue worker via graph.NewQueuedExecutor with a resolver that returns it for SubAgentGraphID; run the same wiring on every worker so orphaned subagent runs recover onto another node.

Only registered subagents are runnable this way: a worker rebuilds the subagent from svc by name, so the definition must exist in every worker's service. Dynamic subagents (defined inline at spawn time) are not reconstructable remotely and must use InProcessRunner.

Types

type DeepAgentConfig

type DeepAgentConfig struct {
	// ID and Name identify the agent; they default to "deep-agent"/"Deep Agent".
	ID   string
	Name string

	// Model is the LLM provider driving the loop. Required.
	Model model.Provider

	// Storage, when set, makes the plan and virtual filesystem durable and
	// enables session compaction via Agent.ChatWithSession. It must implement
	// storage.SessionFileStore (sqlite and postgres do) to back the VFS. When nil,
	// the plan and VFS are in-memory (ephemeral) and compaction is unavailable
	// (compaction needs a session), so drive the agent with Agent.Chat.
	//
	// Note: the plan and VFS tools are session-scoped even in memory, so any call
	// path must carry a session-scoped context (storage.WithSession); ChatWithSession
	// does this automatically. For storageless use, wrap the context yourself:
	//	ctx = storage.WithSession(ctx, "some-session-id")
	Storage storage.Storage

	// MemoryManager, when set, is attached so cross-session semantic recall
	// (WC-D-001) is available; recall is on by default when the manager has a
	// vector index.
	MemoryManager *memory.Manager

	// Broker, when set, receives plan-update stream events.
	Broker *stream.Broker

	// SystemPrompt overrides DefaultDeepAgentSystemPrompt.
	SystemPrompt string

	// Instructions are appended as additional system-level guidance.
	Instructions []string

	// SubAgents pre-registers named specialist templates the agent can select by
	// name via spawn_subagent (it can also define subagents dynamically).
	SubAgents []SubAgentSpec

	// MaxSubAgentDepth bounds subagent nesting; <= 0 uses DefaultMaxSubAgentDepth.
	MaxSubAgentDepth int

	// DisableSubAgents omits the spawn_subagent tool entirely.
	DisableSubAgents bool

	// SubAgentRunner overrides the execution strategy for subagents; nil uses an
	// in-process runner. Pass a QueuedRunner for durable, relocatable subagents.
	SubAgentRunner Runner

	// ExtraTools and ExtraToolkits add domain tools (e.g. web search, SQL) on top
	// of the harness primitives. Subagents may be granted these by name.
	ExtraTools    []*tool.Definition
	ExtraToolkits []*tool.Toolkit

	// Context overrides the compaction policy; nil uses defaultDeepAgentContext.
	// The active plan is pinned regardless, so it is never summarized away.
	Context *agent.ContextConfig
}

DeepAgentConfig configures NewDeepAgent. Only Model is required; every other field has a sensible default, and each capability can be overridden or turned off, so the preset is batteries-included but not a black box.

type InProcessRunner

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

InProcessRunner runs the subagent directly in the caller's process via the standard agent loop. It is the default and the only option for dynamic subagents (whose definition cannot be reconstructed on another worker).

func NewInProcessRunner

func NewInProcessRunner(svc *SubAgentService) *InProcessRunner

NewInProcessRunner creates an in-process runner over svc.

func (*InProcessRunner) Run

func (r *InProcessRunner) Run(ctx context.Context, spec SubAgentSpec, task string) (string, error)

Run builds spec via the service and executes it on task in a fresh conversation.

type Option

type Option func(*SubAgentService)

Option configures a SubAgentService.

func WithMaxDepth

func WithMaxDepth(n int) Option

WithMaxDepth overrides the maximum subagent nesting depth. A value <= 0 restores DefaultMaxSubAgentDepth.

type QueuedOption

type QueuedOption func(*QueuedRunner)

QueuedOption configures a QueuedRunner.

func WithPollInterval

func WithPollInterval(d time.Duration) QueuedOption

WithPollInterval sets how often the runner polls for completion. A value <= 0 restores the default.

func WithTimeout

func WithTimeout(d time.Duration) QueuedOption

WithTimeout bounds how long Run waits for a worker to complete the subagent. Without it (or with 0) Run waits until the caller's context is done — so a caller passing a non-cancelable context with no worker draining the queue would block forever; set a timeout in that case.

type QueuedRunner

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

QueuedRunner runs a subagent as a durable queued graph run: it enqueues the invocation and waits for a worker to complete it, reading the result from the run's final checkpoint. Because the work lives in the durable queue, killing the worker mid-run lets the reaper re-lease it to another worker, which completes it — the subagent is resumable and relocatable.

It requires the subagent to be registered with the service (see NewSubAgentGraph); dynamic subagents are rejected because a remote worker cannot reconstruct them.

func NewQueuedRunner

func NewQueuedRunner(svc *SubAgentService, q *queue.Queue, store storage.Storage, opts ...QueuedOption) *QueuedRunner

NewQueuedRunner creates a durable subagent runner over svc, the queue q, and the checkpoint store the workers use.

func (*QueuedRunner) Run

func (r *QueuedRunner) Run(ctx context.Context, spec SubAgentSpec, task string) (string, error)

Run enqueues spec as a durable subagent run and blocks until a worker finishes it (or the context/timeout elapses), returning the subagent's result.

type Runner

type Runner interface {
	Run(ctx context.Context, spec SubAgentSpec, task string) (string, error)
}

Runner executes a subagent described by spec on a task and returns only its final result. Each strategy runs the subagent where it belongs: InProcessRunner runs it in the caller's process; the durable QueuedRunner enqueues it so a worker runs it, resumable across worker restarts. Both return just the result string, so the subagent's intermediate tokens never reach the parent's context.

type SubAgentService

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

SubAgentService builds subagents that inherit the parent's model but run in a fresh, isolated conversation. It resolves pre-registered specs by name and builds dynamic ones on demand, granting each subagent only an explicit subset of the parent's tools.

It is a registry and is safe for concurrent use: Register may be called while spawns are in flight, mirroring tool.Registry and skill.Registry. model and tools are set once at construction and never mutated.

func NewSubAgentService

func NewSubAgentService(parent *agent.Agent, opts ...Option) (*SubAgentService, error)

NewSubAgentService creates a service that builds subagents inheriting parent's model and drawing tools from parent's registry. parent must have a model.

func (*SubAgentService) Register

func (s *SubAgentService) Register(spec SubAgentSpec) error

Register adds a named subagent template the parent can select at spawn time.

func (*SubAgentService) Registered

func (s *SubAgentService) Registered() []string

Registered returns the names of all pre-registered subagents, sorted for deterministic output.

type SubAgentSpec

type SubAgentSpec struct {
	// Name identifies the subagent (used as its agent id and, for registered
	// specs, the key the parent selects it by).
	Name string `json:"name"`
	// Description is a human-readable summary surfaced to the model.
	Description string `json:"description,omitempty"`
	// SystemPrompt is the subagent's system prompt; it defines its role.
	SystemPrompt string `json:"system_prompt,omitempty"`
	// Instructions are additional system-level instructions.
	Instructions []string `json:"instructions,omitempty"`
	// ToolNames selects which of the parent's tools the subagent may use. An
	// empty list gives the subagent no tools (pure reasoning). Names not present
	// in the parent registry are rejected at build time.
	ToolNames []string `json:"tool_names,omitempty"`
}

SubAgentSpec describes a subagent to run. A spec is either pre-registered with a SubAgentService (selected by Name at spawn time) or supplied dynamically in the spawn_subagent call, so a parent can invent a specialist for a task at runtime rather than only at build time.

Jump to

Keyboard shortcuts

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