Help us shape the future of Go! Take the 2026 Go Developer Survey

executor

package
v0.2.0 Latest Latest
Warning

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

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

Documentation

Overview

Package executor is the hands' consumer: it pulls tool_exec work from the queue, runs the built-in toolset inside the session's sandbox, and appends the agent.tool_result events the brain resumes on. Platform-managed cloud and customer BYOC are the same pull protocol at two deployment points; this is the platform-managed one, embedding the Docker sandbox provider.

The loop mirrors the brain's: Claim the oldest tool_exec item (reclaiming an expired lease), do the work, hand the item back. The brain, when a turn stops for a built-in tool, commits the agent.tool_use intents and enqueues one tool_exec item; this executor answers every unanswered agent.tool_use for the session, then — once the set is complete — enqueues the model_turn that wakes the brain to continue. The result append, the resume enqueue, and the item's completion are one transaction under the session row lock, so a concurrent trigger never sees a gap.

At-most-once is the queue's lease, not a marker in the sandbox (which is agent-writable and disposable — see internal/sandbox/shell). A lease keeper holds the claim while tools run so two executors never run one session's tools at once; a crash mid-run lets the lease lapse, and the reclaiming executor re-runs only the still-unanswered tools — a committed result is never re-run, so a tool's result is exactly-once even though a non-idempotent command can run more than once across a crash. That residue is inherent to a disposable sandbox and is documented, not solved here.

Index

Constants

View Source
const (
	// MetricCheckpoints counts capture attempts by outcome; the duration
	// histogram shares its name with a ".duration" suffix.
	MetricCheckpoints = "sandbox.checkpoint"
	// MetricRestores counts restore attempts by outcome.
	MetricRestores = "sandbox.restore"
)
View Source
const (
	// MetricSkillsMaterialized counts per-skill materialization outcomes.
	MetricSkillsMaterialized = "skills.materialized"
	// MetricSkillsMaterializeDuration is one whole materialization pass.
	MetricSkillsMaterializeDuration = "skills.materialize.duration"
	// MetricFilesMaterialized counts per-file mount materialization outcomes.
	MetricFilesMaterialized = "files.materialized"
	// MetricFilesMaterializeDuration is one whole file-materialization pass.
	MetricFilesMaterializeDuration = "files.materialize.duration"
	// MetricReposMaterialized counts per-repository materialization outcomes.
	MetricReposMaterialized = "repos.materialized"
	// MetricReposMaterializeDuration is one whole repo-materialization pass.
	MetricReposMaterializeDuration = "repos.materialize.duration"
	// MetricReposMaterializeBytes is one landed repository's shipped size.
	MetricReposMaterializeBytes = "repos.materialize.bytes"
)
View Source
const MetricSessionsReaped = "sandbox.sessions.reaped"

MetricSessionsReaped counts reaped sessions by tier.

Variables

View Source
var ErrCheckpointTooLarge = errors.New("executor: checkpoint exceeds the configured size budget")

ErrCheckpointTooLarge reports a capture whose members exceed the configured budget. The TTL tier treats it as "reap without checkpoint" — an agent must not pin its sandbox immortal by filling the disk (plan 24 D8).

Functions

func GateTokenRevoker added in v0.2.0

func GateTokenRevoker(pool *pgxpool.Pool) sandbox.GateTokenRevoker

GateTokenRevoker is the pool-backed sandbox.GateTokenRevoker — the same implementation the executor injects into every Spec — exported for the provider construction in cmd glue, where the sandbox backend is built with a revoker so Reap can revoke a session's gate token before removing its containers (plan 24).

func ValidateWorkdir added in v0.2.0

func ValidateWorkdir(workdir string) error

ValidateWorkdir refuses a configured workdir that would alias the checkpoint's other roots or its own machinery: a workdir under (or over) the shell-state root or /mnt/session double-captures and double-charges the shared subtree, and one under /tmp would put the restore staging file — and state the checkpoint deliberately drops — inside the archive. cmd/executor calls this at startup; a violation is configuration, not data.

Types

type Config

type Config struct {
	Image        string
	Workdir      string
	LeaseTTL     time.Duration
	PollInterval time.Duration
	// ReapInterval paces the sandbox reaper (reaper.go): one sweep of this
	// endpoint's owned sessions per interval (EXECUTOR_REAP_INTERVAL; 0 takes
	// the 60s default). Teardown latency is bounded by it, and nothing else
	// destroys sandboxes, so there is no off switch — a deployment that wants
	// slower reaping sets it longer.
	ReapInterval time.Duration
	// CheckpointMaxBytes budgets a workspace checkpoint (checkpoint.go): ONE
	// measure on both sides — the framed, uncompressed tar stream, metered as
	// capture writes it and again as restore decompresses it — so a capture
	// that fits is arithmetically guaranteed to restore
	// (EXECUTOR_CHECKPOINT_MAX_BYTES; 0 takes the 2 GiB default). Over
	// budget, the TTL tier reaps without a checkpoint — an agent must not
	// pin its sandbox immortal by filling the disk (plan 24 D8).
	CheckpointMaxBytes int64
	// SandboxIdleTTL arms the reaper's idle tier (plan 24 slice 5): an idle
	// cloud session whose last activity is older than this is checkpointed and
	// its sandbox reaped — unless it still owes work (a queued/starting/active
	// work item) or an unanswered confirmation ask (HITL-idle is mid-turn).
	// Zero disables the tier — deliberately, so a hand-built test Config never
	// reaps by surprise; cmd/executor resolves the unset env to the 24h
	// default (EXECUTOR_SANDBOX_IDLE_TTL; 0 there disables too). A blob-less
	// executor disables the tier at startup regardless: reaping without the
	// means to checkpoint would silently discard workspaces.
	SandboxIdleTTL time.Duration
	// Hardening is the containment every session's sandbox is created with —
	// cgroup limits, capability drops, optionally a uid and a read-only root
	// (#65). The zero value hardens nothing, which is what a test that builds a
	// Config by hand wants; cmd/executor resolves the platform's defaults from
	// the environment (sandbox.HardeningFromEnv), so every deployment gets them.
	Hardening sandbox.Hardening
	// ControlplaneURL and GateImage opt the deployment into the per-session egress
	// gate. A session wants a gate when its networking is `limited` or it has
	// vaults attached; its gate container (GateImage) fetches that session's egress
	// config from ControlplaneURL. When both are set, a gate-wanting session gets a
	// gate; when either is empty, no gate is requested and a gate-wanting session
	// keeps the backend's own fail-closed networking (Docker `limited` → no egress,
	// K8s → its init-container isolation, vault-attached → inert placeholders) — the
	// pre-gate behavior, so an un-opted-in deployment is unchanged rather than
	// faulted. An unrestricted, vault-less session never wanted a gate and networks
	// directly regardless. See gateSpec.
	ControlplaneURL string
	GateImage       string
	// OTelEndpoint and OTelInsecure are the deployment's OTLP collector config,
	// threaded into each session's gate container so its egress_request spans
	// export to the same collector as the executor (the gate is a separate process
	// that does not inherit this executor's environment). Empty OTelEndpoint =
	// no collector; the gate runs without an exporter.
	OTelEndpoint string
	OTelInsecure bool
	// The web tools' backends (docs/plan/15_web-tools.md). An unconfigured
	// tool answers with an is_error result naming what is missing, so the
	// misconfiguration surfaces to an operator instead of masking the tool.
	// web_search needs TavilyAPIKey; web_fetch needs JinaAPIKey OR an explicit
	// WebFetchBaseURL (the Reader protocol works keyless — a keyless proxy, or
	// the public free tier named deliberately — but with neither set, a bare
	// install must not silently egress model-chosen URLs to a public third
	// party). The base URLs point at Tavily-protocol / Jina-Reader-protocol
	// endpoints; empty resolves to the adapters' public defaults.
	TavilyAPIKey     string
	JinaAPIKey       string
	WebSearchBaseURL string
	WebFetchBaseURL  string
	// WebAllowedDomains, when non-empty, is the operator-side allowlist for
	// the web tools (#225): web_fetch may reach only these hosts, and a
	// search hit whose source is outside them is dropped. Entries use the
	// same grammar as the wire's allowed_hosts (a bare hostname, an IPv4
	// literal, or a "*."-prefixed wildcard that never matches the apex —
	// egress.HostSet, the one matcher). Empty means unrestricted: the
	// reference has per-tool allowed domains, but no wire field carries
	// them, so this knob is platform-native (docs/DIVERGENCES.md).
	WebAllowedDomains []string
	// The clone budgets for github_repository mounts (plan 25 decision 1).
	// The spool a clone lands in sits on executor-local disk, outside the
	// sandbox's own storage hardening, so one unbounded repository could
	// exhaust the executor and disrupt unrelated sessions. RepoCloneMaxBytes
	// is metered as bytes land — over the tree and its tar together — and
	// RepoCloneTimeout bounds one repository's clone; both surface as
	// tolerated clone failures (too_large / timeout), never as a failed run.
	RepoCloneTimeout  time.Duration
	RepoCloneMaxBytes int64
}

Config tunes the loop. Image is the sandbox base image (a deployment choice — the wire's environment config has no image field). LeaseTTL must comfortably exceed toolset.MaxTimeout: the lease keeper renews at TTL/3 while a tool runs, but the TTL is also the window a crashed executor's work waits before another reclaims it.

type Executor

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

Executor consumes tool_exec work over one Postgres pool and one sandbox provider.

func New

func New(pool *pgxpool.Pool, log *events.Log, q *queue.Queue, provider sandbox.Provider, blobs blob.Store, cipher secrets.Cipher, cfg Config) *Executor

func (*Executor) Run

func (e *Executor) Run(ctx context.Context) error

Run polls until the context is cancelled. It claims one tool_exec item at a time; an error processing one item is logged by returning it up to the caller only for a fatal claim failure — a per-item fault is swallowed so the loop keeps serving other sessions, and the faulted item is reclaimed after its lease lapses.

Jump to

Keyboard shortcuts

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