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