Skip to documentation
Documentation navigation

Documentation navigation

Documentation / guides

Bindings and roots

Bind tools to canonical Session-owned workspace roots.

developer

The composition root chooses a placement; individual tools receive only the capabilities their definition requested. A workspace-bound tool gets a WorkspaceBinding with a canonical root, a mutation coordinator, and optional same-loop file observations. A read-only evidence tool gets a ReadWorkspaceBinding with only a root string.

Placement options

func WithExclusiveWorkspace(
	store *workspacestore.Store,
	root string,
	leaser storage.Leaser,
) rig.Option

func WithSessionWorkspaces(
	store *workspacestore.Store,
	baseDir string,
) rig.Option

func WithSharedWorkspace(
	store *workspacestore.Store,
	root string,
) rig.Option

Exactly one of these options may be present. WithExclusiveWorkspace and WithSharedWorkspace name a fixed root. WithSessionWorkspaces names a base directory and derives an injective baseDir/<sessionID> destination for every non-zero session ID. A workspace store or exclusive leaser that is nil is rejected at definition time.

Canonical roots and lease names

Define turns a root or base directory into an absolute, clean path. If the path or an existing parent can be resolved, EvalSymlinks is applied so lexical and symlink aliases converge. Exclusive placement derives the storage lease name as:

workspace-roots/<sha256(canonical-root) in lowercase hex>

The canonical root and mode also enter the rig fingerprint. Changing either is a durable configuration change, not a runtime-only detail. A root lease is acquired only after the session journal lease has been acquired.

Tool bindings

The public binding structs are intentionally narrow:

type WorkspaceBinding struct {
	Root         string
	Coordinator  WorkspaceCoordinator
	Observations WorkspaceObservations
}

type ReadWorkspaceBinding struct {
	Root string
}

type WorkspaceCoordinator interface {
	Acquire(context.Context, WorkspaceOperation, string) (WorkspacePermit, error)
	Healthy() error
}

type WorkspacePermit interface { Release() }

WorkspaceOperationPathMutation requires a non-empty canonical path and serializes overlapping scopes. WorkspaceOperationWholeMutation is an exclusive whole-tree permit. WorkspaceOperationCheckpoint is the distinct exclusive snapshot/restore permit and requires an empty path. A canceled wait returns a typed acquisition error and leaves no permit behind. A mutator must check Healthy before committing; after exclusive lease loss it fails closed.

WorkspaceObservations is optional shared state for one loop’s file tools and Bash. It does not grant authority or escape the root.

type Bindings struct {
	SessionID     uuid.UUID
	LoopID        uuid.UUID
	Workspace     *WorkspaceBinding
	ReadWorkspace *ReadWorkspaceBinding
	Delegate      DelegateController
	Process       *ProcessBinding
}

tool.RequiresWorkspace requires Workspace != nil, a non-empty root, and a healthy coordinator. tool.RequiresWorkspaceRead requires an absolute clean read root and exposes no mutation capability. Build calls receive a defensive attenuation of Bindings, so a definition cannot retain unrelated session authority.

Persistence boundary

Session journal paths, workspace blob-provider paths, and the snapshot spool directory must be outside the managed region. Equality and descendants count as overlap; an ancestor or sibling does not. Define fails with *rig.PersistenceOverlapError, because a checkpoint must not mutate the tree it is walking by appending its own journal record.

The source is pkg/rig/workspace.go, internal/sessionruntime/workspace_placement.go, and pkg/tool/definition.go. Canonicalization, alias convergence, overlap, and binding failures are covered by pkg/rig/workspace_test.go and pkg/tool/definition_test.go.

Source and proof

← back to documentation