composition

package
v0.1.3 Latest Latest
Warning

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

Go to latest
Published: Aug 30, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package composition owns session wiring: registries, dispatchers, hooks, MCP merge, session construction. internal/cli must not hold this logic; it parses arguments and renders output.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AttachMCPServers

func AttachMCPServers(reg *tools.Registry, cfg config.MCPConfig, redactionPolicy *redact.Policy, serverIDs []string) (*mcp.Manager, func(), error)

AttachMCPServers builds an MCP manager for cfg and merges serverIDs' tools into reg (see MergeMCPTools). It returns the manager (nil when MCP is disabled) and a cleanup that closes it. On error the manager, if created, is closed before returning so a failed attach leaks no client.

func BuildDispatcher

func BuildDispatcher(in DispatcherInput) (*runtime.Dispatcher, error)

BuildDispatcher assembles the runtime.Dispatcher with hook gates. It fills Policy.PreInvokeHook/PostInvokeHook exactly as internal/cli/hooks_runner.go did before this move: nil when no hooks are configured, otherwise closures that run the session's hook groups through a hooks.Runner rooted at WorkspaceRoot.

func BuildRegistry

func BuildRegistry(in RegistryInput) (*tools.Registry, error)

BuildRegistry constructs the tool registry. It produces a registry identical to what the cli path built before the composition-root move (internal/cli's buildWorkflowToolOpts/configureChatWorkspace and workflowDefaultRegistry). It never returns a non-nil error today because tools.NewDefaultRegistry cannot fail; the error return is kept so a future validation step does not change this function's signature.

func BuildSession

BuildSession wires a chat.Session end to end: the completer chat.NewSession takes directly, the tool registry (BuildRegistry), the dispatcher (BuildDispatcher), the event bus, and a SQLite-backed checkpoint store. It returns the checkpoint principal actually installed (in.Principal as-is, or a freshly minted one), so a caller can read its own checkpoint back through the returned store. The caller owns the store's Close; the session holds no reference that outlives it.

func HookPolicyFuncs

HookPolicyFuncs returns the dispatcher's lifecycle-hook fields, or nils when no hook is configured at all. Exported so a caller that needs the funcs without a full dispatcher (e.g. a session that rebuilds Policy directly) can still delegate to this package rather than reimplement the hook-execution logic.

Nil is not an optimisation, it is the contract: with no hooks configured the dispatcher does one nil compare per invocation and behaves exactly as it did before this layer existed.

func MergeMCPTools

func MergeMCPTools(reg *tools.Registry, manager *mcp.Manager, serverIDs []string) error

MergeMCPTools discovers each of serverIDs' tools through manager and registers them into reg under the manager's mcp__<server>__<tool> name encoding (see internal/mcp's discoveredTool.Name). A contained server outage never fails the merge; the session continues without that server's tools, and the operator is warned by server ID only - external error text can carry request content (DC-14). A discovered tool whose name collides with a name reg already holds is rejected as an error unless manager owns that name (an idempotent re-merge of the same server).

func NewMCPManager

func NewMCPManager(cfg config.MCPConfig, redactionPolicy *redact.Policy) (*mcp.Manager, error)

NewMCPManager builds an MCP manager from cfg. It returns (nil, nil) when cfg.Enabled is false, matching the historical cli behavior: a disabled MCP configuration is a no-op, not an error.

Types

type DispatcherInput

type DispatcherInput struct {
	// Registry is the tool registry the dispatcher registers handlers
	// against.
	Registry *tools.Registry

	MaxDepth, MaxRetries, MaxInputBytes, MaxOutputBytes int
	MaxBudget                                           int
	// Allow is the per-Kind, per-name allow map. Nil allows every
	// registered handler, the same default runtime.Policy carries today.
	Allow map[runtime.Kind]map[string]bool
	// Sink, when set, receives one runtime.Event per invocation lifecycle
	// step.
	Sink func(runtime.Event)

	// WorkspaceRoot is the directory lifecycle hooks execute in. Empty
	// means no hooks are wired at all, the same as HooksConfigured false.
	WorkspaceRoot string
	// HooksConfigured reports whether this session has any hook armed,
	// evaluated once at build time. False (or an empty WorkspaceRoot)
	// means Policy.PreInvokeHook/PostInvokeHook stay nil: one nil compare
	// per invocation, no hook overhead at all - the same contract
	// internal/cli/hooks_runner.go's hookPolicyFuncs held before this
	// move.
	HooksConfigured bool
	// HookGroups returns the runnable hook groups for the current session.
	// It is read fresh on every hook invocation rather than closed over at
	// build time, so what the caller's session state lists is what the
	// next tool call runs without a dispatcher rebuild. Required when
	// HooksConfigured is true.
	HookGroups func() []hooks.Group
	// NoteHookWarnings receives runtime diagnostics from hooks that
	// actually executed, for the caller to surface (e.g. a /hooks
	// listing). Nil is safe: warnings are simply dropped.
	NoteHookWarnings func([]string)
}

DispatcherInput carries the tool registry, dispatcher policy, and hook wiring BuildDispatcher needs to assemble a runtime.Dispatcher. The policy fields mirror runtime.Policy field for field; BuildDispatcher is the only place that translates one into the other.

type RegistryInput

type RegistryInput struct {
	Workspace *workspace.Root

	RunAllowlist, RunAllowlistOnly, RunBlocklist, DisableTools                                   []string
	RunTimeoutSec, MaxReadBytes, MaxEditFileBytes, MaxOutputBytes, MaxWriteKB, MaxListDirEntries int

	MaxToolResultBytes                           int
	MaxTavilyResponseBytes                       int
	MaxFetchKB                                   int
	MemoryBackstopBytes                          int
	TavilyAPIKey                                 string
	EnvAllowlist, EnvAllowlistOnly, EnvBlocklist []string
	EnvAllowKeywordBlocklist                     []string
	SecretPathPatterns, SecretPathExceptions     []string

	WritePathDenylist    []string
	SearchIgnorePatterns []string

	MaxInspectRepositoryBytes int

	DiagnosticsCommands map[string][]string

	WorkflowTools []tools.Tool

	Memory memory.Store
}

RegistryInput carries every value the CLI or tests supply to build the default tool registry. Keep it a plain struct. No methods. No behavior. Fields mirror tools.DefaultOptions field for field: BuildRegistry is the only place that translates one into the other.

type SessionInput

type SessionInput struct {
	// Config is the resolved workspace configuration. Required:
	// chat.NewSession reads it for the model, prompt, and token budgets.
	Config *config.Resolved
	// Completer is the provider completer the session's initial binding
	// uses. May be nil (chat.NewSession accepts a nil completer for
	// construction), but a nil completer cannot run a turn.
	Completer provider.Completer

	// Registry configures the tool registry BuildRegistry builds. Its
	// Workspace field may be nil, which yields a registry with no
	// filesystem tools.
	Registry RegistryInput
	// PrebuiltRegistry, when non-nil, skips BuildRegistry(in.Registry)
	// entirely and assigns sess.Tools = in.PrebuiltRegistry directly. Use
	// this when the caller has already merged additional tools (e.g. MCP
	// wrappers via AttachMCPServers) into a registry it owns. The dispatcher's
	// view matches regardless of the path taken: dispatcher.Registry is set
	// to whatever sess.Tools ends up as.
	PrebuiltRegistry *tools.Registry
	// Dispatcher configures the dispatcher BuildDispatcher builds.
	// Dispatcher.Registry is overwritten with the registry BuildSession just
	// built, so the caller does not need to set it.
	Dispatcher DispatcherInput

	// EventBus is the session's event bus. Nil mints a fresh events.New().
	EventBus *events.Bus

	// StorePath is the SQLite file BuildSession opens for the session's
	// checkpoint store. Required: chat.Session has no durable checkpoint
	// history without one.
	StorePath string
	// WorkspaceID identifies the checkpoint principal's workspace scope.
	// Ignored when Principal is already bound.
	WorkspaceID string
	// SubjectID identifies the checkpoint principal's subject scope. Empty
	// defaults to the session's own SessionID. Ignored when Principal is
	// already bound.
	SubjectID string
	// Principal, when bound (IsBound() true), is used as-is instead of
	// minting a fresh one from WorkspaceID/SubjectID. A caller that needs to
	// read its own checkpoint back later (contextstate.Principal's capability
	// is random per mint - see contextstate.NewPrincipal - and store.Load
	// rejects a principal whose capability does not match what was written)
	// must supply the same Principal value it will read with.
	Principal contextstate.Principal
}

SessionInput carries every value BuildSession needs to construct a working chat.Session: the resolved config and completer chat.NewSession itself takes, the registry and dispatcher inputs BuildRegistry/BuildDispatcher already accept, and the checkpoint store BuildSession opens and wires as the session's context store.

Jump to

Keyboard shortcuts

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