agentd

package
v0.37.0 Latest Latest
Warning

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

Go to latest
Published: Oct 11, 2026 License: AGPL-3.0 Imports: 4 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SecretsEnvPath  = "/sandbox-runtime/secrets-env"
	AgentConfigPath = "/sandbox-runtime/agent-config.json"
	AdminPromptPath = "/sandbox-runtime/admin-prompt.md"
	PasswordPath    = "/sandbox-cfg/password"
	SecretsBasePath = "/sandbox-runtime/rt/secrets"
	WorkspacePath   = "/workspace"
	// StagedFilesMaxBytes is the W8 file-class delivery budget: the
	// staged manifest + bytes MUST fit under it because it is also the
	// /v1/spawn-files response cap the supervisor enforces. Binding-time
	// per-secret validation (pkg/secrets) is the user-facing gate; the
	// materializer's per-entry and whole-batch checks are the
	// defense-in-depth ceiling (size_exceeded).
	StagedFilesMaxBytes = 8 << 20
	// AllowedDirsPath is where the bootstrap subcommand writes the instance's
	// allowedExternalDirectories setting (a JSON array of glob patterns). The
	// AgentConfigWriter reads it once at init and merges each pattern into
	// agent-config.json's TOP-LEVEL permission.external_directory (the
	// LIVE key on pinned opencode — mode.permissions is inert) as an
	// "allow" rule, so agents stop prompting for /tmp/* on every
	// session. Lives on
	// /sandbox-runtime tmpfs: survives container restart, wiped on pod
	// death, no plaintext-on-PVC concern (it's a list of public path
	// globs, not secrets).
	AllowedDirsPath = "/sandbox-runtime/allowed-dirs.json"
	// ModelResolutionWarningPath is where the materialize subcommand records
	// that the workspace's persisted default model could not be resolved to
	// any provider in agent-config.json (e.g. a relay-only model selected
	// while the relay injector failed at boot — incident 2026-08-16). The
	// model key is deliberately omitted from agent-config.json in that case
	// (a bare ID poisons opencode's model resolution: every prompt in every
	// session fails with ProviderModelNotFoundError until the pod rebuilds).
	// agentd's healthz/statusz read this marker so the controller can relay
	// the condition into the AgentHealthy condition message the user sees.
	// Same tmpfs lifecycle as agent-config.json; removed by the next
	// successful resolution.
	ModelResolutionWarningPath = "/sandbox-runtime/model-resolution-warning.json"
	// SidecarRestartMarkerPath is where the restart-reason marker lives
	// in sidecar mode (design 0051 US-2): the shared /sandbox-runtime
	// tmpfs. Writers straddle uids (sidecar 2000, supervisor 1000) with
	// the pod's shared group 1000 in common, so markers there are 0640.
	// The controller stamps it into LLMSAFESPACES_RESTART_MARKER_PATH on
	// BOTH containers; env unset keeps the single-container default.
	SidecarRestartMarkerPath = "/sandbox-runtime/last-restart-reason.json"
	// UploadsPath is the Epic 68 file-ingest root on the workspace PVC:
	// every PUT /v1/files upload lands here as <uuid>-<sanitized-name>
	// (atomic tmp+rename, design epic-68 D2/D3). Override via
	// LLMSAFESPACES_UPLOADS_PATH (tests, dev relocatability).
	UploadsPath = "/workspace/uploads"
)

Well-known paths shared between entrypoint scripts and agentd. Shell scripts reference these by convention — keep in sync.

US-35.7: credential output paths point to /sandbox-runtime (tmpfs, RAM-backed) so no plaintext persists on the PVC at rest. $HOME-relative paths (SSH, git, secrets) are symlinks created by the init container pointing into /sandbox-runtime/rt/*.

AdminPromptPath also lives on /sandbox-runtime — both because the merged platform/org/role/user system prompt is sensitive control-surface content (canary tokens, safety guardrails) that should die with the pod, and because the credential-setup init container that writes it has ReadOnlyRootFilesystem without a writable emptyDir at /tmp. Previously (#483) the constant pointed at /tmp/admin-prompt.md and the bootstrap subcommand silently failed every write — the admin prompt never reached opencode on any workspace, breaking the three-tier prompt chain end-to-end.

View Source
const (
	AgentPort       = 4096 // opencode serve listens here
	AgentdPort      = 4097 // agentd user-facing HTTP API (resync-secrets, future proxy)
	AgentdAddr      = "0.0.0.0:4097"
	AgentdAdminPort = 4098 // agentd admin HTTP API (healthz, readyz, statusz) — US-22.8
	AgentdAdminAddr = "0.0.0.0:4098"
	AuthUsername    = "opencode" // Basic Auth username for opencode
)

Ports and network constants shared between agentd and the controller.

View Source
const DeliveryCapability = "v2"

DeliveryCapability is this build's secret-delivery generation (US-70.5 fleet-version evidence): "v2" marks the conditional-pull stack — the v2 bootstrap envelope contract, revision anchoring, the resync endpoint, and spawn-rev terminal verification. Pre-US-70.2 runtimes omit the healthz delivery field entirely, so a fleet scrape can count pods still on the legacy stack (the W15 mixed-fleet gauge). It is a capability statement only — convergence is still judged from the revision signals (spawned_rev), never from this field.

View Source
const MaxFilenameBytes = 200

MaxFilenameBytes is the sanitized-filename byte cap applied by both upload layers (agentd PUT /v1/files authoritative, API defense-in-depth — design epic-68 D9). Truncation lands on a rune boundary.

View Source
const OverlayMountPath = "/agentd"

OverlayMountPath is the agentd image-volume mount inside workspace pods (controller internal mirror: agentdMountPath). Shared here so the #1573 baked-path self-denial — the binary refuses to run from outside this mount when the overlay marker is set — judges the same prefix the controller mounts.

Variables

This section is empty.

Functions

func IsForbiddenFilenameRune added in v0.24.0

func IsForbiddenFilenameRune(r rune) bool

IsForbiddenFilenameRune reports whether r is stripped during filename sanitization: quote characters, path separators (backslash arrives here only when residual after the separator split), bidi/RTL overrides, and control characters.

func SanitizeFilename added in v0.24.0

func SanitizeFilename(raw string) (string, bool)

SanitizeFilename flattens an untrusted filename to a safe base name. It is the single source shared by agentd's PUT /v1/files handler (authoritative) and the API's upload proxy (defense-in-depth), so the two layers cannot drift:

  1. basename — both path separators (/ and \) are split so Windows-shaped traversal cannot survive
  2. strip control characters (C0/C1 incl. \n \r \x1b), bidi/RTL overrides (U+202A-U+202E, U+2066-U+2069), quotes, backslashes, and any residual slash
  3. trim trailing dots and spaces (Windows-hostile suffixes)
  4. truncate to MaxFilenameBytes on a rune boundary, re-trim
  5. reject empty or whitespace-only results (ok=false)

Types

type AgentzResponse added in v0.35.0

type AgentzResponse struct {
	OK                 bool   `json:"ok"`
	State              string `json:"state"`
	EpisodeSeconds     int    `json:"episodeSeconds,omitempty"`
	SustainSeconds     int    `json:"sustainSeconds,omitempty"`
	ConsecutiveHealthy int    `json:"consecutiveHealthy,omitempty"`
	LastError          string `json:"lastError,omitempty"`
}

AgentzResponse is the /v1/agentz payload (#1632 fix #3): the agent-health-derived LIVENESS answer. OK is the probe decision (true = 200, false = 503); State names the branch ("no-evidence" | "unhealthy-episode" | "unhealthy-episode-sustained"); EpisodeSeconds is the current unhealthy-episode age when one is open; SustainSeconds echoes the configured bound for observability.

type CPUUsage

type CPUUsage struct {
	// UsageMicros is cumulative CPU microseconds consumed (cpu.stat usage_usec).
	// Monotonically increasing — callers compute delta between polls for rate.
	UsageMicros int64 `json:"usage_micros"`
	// LimitMicrosPerSec is the CPU quota in µs/s from cpu.max (quota/period×1e6).
	// 0 means no quota (unlimited).
	LimitMicrosPerSec int64 `json:"limit_micros_per_sec,omitempty"`
}

CPUUsage reports cumulative CPU consumption from cgroup v2 cpu.stat.

type ContextUsage

type ContextUsage struct {
	UsedTokens  int64 `json:"used_tokens"`
	TotalTokens int64 `json:"total_tokens"`
}

ContextUsage reports LLM context window usage across active sessions.

type DiskUsage

type DiskUsage struct {
	UsedBytes  int64 `json:"used_bytes"`
	TotalBytes int64 `json:"total_bytes"`
}

DiskUsage reports workspace filesystem usage.

type FileUploadResponse added in v0.23.0

type FileUploadResponse struct {
	Path string `json:"path"`
	Name string `json:"name"`
	Size int64  `json:"size"`
}

FileUploadResponse is the response for PUT /v1/files (Epic 68 US-68.1). Path is the absolute final location (/workspace/uploads/<uuid>-<name> in production); Name is the sanitized filename; Size the byte count on disk. Error responses never echo .tmp or internal paths (design U1.1.20).

type HealthzResponse

type HealthzResponse struct {
	Healthy       bool   `json:"healthy"`
	Version       string `json:"version"`
	UptimeSeconds int    `json:"uptime_seconds"`
	// Delivery is the build's delivery capability marker
	// (DeliveryCapability; absent on pre-US-70.2 runtimes).
	Delivery string `json:"delivery,omitempty"`
	// CommitSHA is the git commit this agentd binary was built from
	// (pkg/version, -ldflags injected; buildinfo vcs fallback). Surfaced
	// so a deployed agentd can be traced to source without binary
	// forensics — a devel hash build reporting a clean release Version
	// (incident 2026-08-15) must be distinguishable from the real tag.
	// "unknown" means neither ldflags nor vcs buildinfo could supply it.
	CommitSHA string `json:"commit_sha,omitempty"`
	// BuildTime is the UTC build timestamp (pkg/version). Same purpose
	// as CommitSHA; "unknown" when not stamped via ldflags (unstamped
	// builds never emit an empty string, so the omitempty tags on both
	// fields are inert by design — kept for forward-compatibility if the
	// defaults ever change).
	BuildTime string `json:"build_time,omitempty"`
	// Warnings are boot-time degradation notices (e.g. the persisted
	// default model could not be resolved — ModelResolutionWarningPath).
	// Observability only: never affects Healthy, never gates liveness.
	// The controller relays them into the AgentHealthy condition message
	// so users see why their selected model is not in use.
	Warnings []string `json:"warnings,omitempty"`
	// SpawnEnv (US-70.1, design 0057) is the supervisor's
	// terminal-verified spawn-env state: the revision of the secrets
	// delta the agent process actually spawned with, plus a
	// machine-readable degrade reason when delivery is incomplete.
	// Nil when the reporter has no supervisor evidence yet
	// (single-container mode, or before the first status poll) — no
	// evidence is not a degrade. Observability only, like Warnings.
	SpawnEnv *SpawnEnvHealth `json:"spawnEnv,omitempty"`
	// PendingApply (#1342, L11) is the deferred-credential-apply state:
	// a restart-worthy credential change staged on the pod but its
	// applying restart is riding a maintenance window behind busy
	// sessions. Nil when nothing is pending (the credential applied, or
	// no restart-worthy change arrived). The controller mirrors it into
	// the Workspace CRD's CredentialsApplyPending condition.
	PendingApply *PendingApplyHealth `json:"pendingApply,omitempty"`
	// Relay (US-72.4, design 0058 §4.5) is the relay-only liveness
	// slice: cached monitor state over the batch's relay-fronted
	// providers. Nil when the monitor is not wired (tests); a non-nil
	// RelayHealth with Present=false is a flag-off pod. Observability
	// only — a relay degrade never flips Healthy (it must not cascade to
	// a liveness-probe kill; the §4.8 remedy is bounded re-arm).
	Relay *RelayHealth `json:"relay,omitempty"`
	// LegacyScrub (US-72.6, design 0058 §8) is the one-time legacy-key
	// migration scrub's outcome. Since the run-36135708380 fix the scrub
	// fires UNCONDITIONALLY at boot (the #1537 first-relay-Present hook
	// alone was starved by design 0061 M2's migration-mode fail-open
	// fallback, which can deliver a raw batch — no relay-fronted entries
	// — on any unconverged boot, exactly when the residue migration must
	// run), so the report is non-nil on EVERY posture once boot completes
	// (flag-off and raw-batch pods included; AlreadyClean on clean roots).
	// Nil only before the boot call lands (early boot) or in topologies
	// that never construct the tracker. Observability only.
	LegacyScrub *LegacyScrubHealth `json:"legacyScrub,omitempty"`
}

HealthzResponse is the response for GET /v1/healthz.

type LegacyScrubHealth added in v0.34.8

type LegacyScrubHealth struct {
	RanAt             int64  `json:"ranAt"`
	AuthKeysRemoved   int    `json:"authKeysRemoved"`
	ConfigKeysRemoved int    `json:"configKeysRemoved"`
	Error             string `json:"error,omitempty"`
}

LegacyScrubHealth is the US-72.6 scrub slice of HealthzResponse: the one-time removal counts and any error. The controller mirrors it into the LegacyKeysScrubbed condition and emits the event on first observation (idempotent thereafter — the report is static).

type MemoryUsage

type MemoryUsage struct {
	UsedBytes  int64 `json:"used_bytes"`
	TotalBytes int64 `json:"total_bytes"`
}

MemoryUsage reports workspace memory usage.

type PendingApplyHealth added in v0.30.1

type PendingApplyHealth struct {
	Reason         string `json:"reason,omitempty"`
	WaitingSeconds int    `json:"waitingSeconds"`
	BusySessions   int    `json:"busySessions"`
}

PendingApplyHealth is the deferred-apply slice of HealthzResponse. Reason is the closed reason set's first value ("credential_change"); WaitingSeconds/BusySessions tell the operator how long and why.

type ReadyzResponse

type ReadyzResponse struct {
	Ready               bool     `json:"ready"`
	ProvidersConnected  []string `json:"providers_connected"`
	ProvidersConfigured int      `json:"providers_configured"`
	AgentVersion        string   `json:"agent_version"`
	AgentType           string   `json:"agent_type"`
	// RelayInjected is true when the writer holds relay state — i.e. the
	// relay config block has been applied (via pre-boot injection, a
	// successful injector run, or a successful #910 re-arm cycle
	// mid-pod-life; evaluated live per request, not latched at boot).
	// The opencode restart that LOADS the config may still be pending:
	// session-aware deferral holds it until sessions idle, and the
	// re-arm path skips its own kill entirely when another deferred
	// restart is outstanding. False before any apply, when skipped
	// (personal opencode key), or while every attempt keeps failing.
	// Known corner (pre-existing, documented): a terminal auth.json
	// write failure after a successful config apply leaves this true
	// with the opencode-relay auth entry missing. Included here (readyz)
	// rather than statusz: readyz is cache-based and lightweight (no
	// synchronous opencode calls), and the API server needs this flag
	// on every ListModels cache miss — using statusz (which has no
	// latency upper bound) would be unsafe.
	RelayInjected bool `json:"relay_injected"`
	// Relay (US-72.4): the relay-only liveness degrade codes
	// (relay_unreachable / token_expired / router reject reasons).
	// Observability only — NEVER gates Ready: a relay outage must not
	// drop the pod from Service endpoints (that would compound the
	// outage); the remedy is the bounded re-arm + conditions.
	Relay *RelayHealth `json:"relay,omitempty"`
}

ReadyzResponse is the response for GET /v1/readyz.

type RelayHealth added in v0.34.7

type RelayHealth struct {
	// Present reports whether relay-fronted providers were observed in
	// the applied batch (the entry metadata the US-72.4 builder emits).
	// False on flag-off pods: nothing to watch, never a degrade.
	Present bool `json:"present"`
	// Reachable reports the last credential-level probe outcome (GET
	// {baseURL}/models with the scoped token — served from the router's
	// staged catalog, zero upstream).
	Reachable bool `json:"reachable"`
	// DegradedReason is the active degrade code ("" healthy):
	// relay_unreachable | token_expired — plus the router's
	// machine-readable reject reasons when a probe was rejected
	// (credential_stale, scope_violation, sanitization_refused,
	// quota_exceeded) — the CredentialRejected/CredentialStale feeds.
	DegradedReason string `json:"degradedReason,omitempty"`
	// RouterURL is the router origin the monitor probes (no path, no
	// token material).
	RouterURL string `json:"routerUrl,omitempty"`
	// TokenExpiresAt is the earliest RFC3339 expiry across the batch's
	// relay tokens ("" when unknown).
	TokenExpiresAt string `json:"tokenExpiresAt,omitempty"`
	// AppliedRevision is the staged relay revision of the applied batch
	// (the entry metadata's relayRevision) — the controller compares it
	// against the workspace's staged-revision annotation (the lineage
	// conjunct of the §4.2 terminator).
	AppliedRevision string `json:"appliedRevision,omitempty"`
	// LastProbeAt is the last probe's unix seconds (0 before the first).
	LastProbeAt int64 `json:"lastProbeAt,omitempty"`
}

RelayHealth is the relay-only liveness slice of healthz/readyz/statusz (US-72.4, design 0058 §4.5/§4.6): cached monitor state only — the handlers perform no I/O. The controller mirrors DegradedReason into the Workspace CRD's SecretsDelivery surface the US-72.3 staging classification reads; AppliedRevision feeds the lineage conjunct.

type SessionInfo

type SessionInfo struct {
	ID          string         `json:"id"`
	Title       string         `json:"title,omitempty"`
	Status      string         `json:"status"` // "idle" | "busy"
	Tokens      *SessionTokens `json:"tokens,omitempty"`
	Model       string         `json:"model,omitempty"` // model ID, e.g. "claude-sonnet-4-20250514"
	ContextUsed int64          `json:"contextUsed"`
}

SessionInfo describes a single opencode session.

type SessionTokens

type SessionTokens struct {
	Input      int64 `json:"input"`
	Output     int64 `json:"output"`
	Reasoning  int64 `json:"reasoning"`
	CacheRead  int64 `json:"cache_read"`
	CacheWrite int64 `json:"cache_write"`
}

SessionTokens describes token usage for a session.

type SpawnEnvHealth added in v0.26.0

type SpawnEnvHealth struct {
	SpawnedRev string `json:"spawnedRev,omitempty"`
	Degraded   bool   `json:"degraded"`
	Reason     string `json:"reason,omitempty"`
	// FilesRev / FilesDegraded / FilesReason (R2b, #1165): the file-class
	// delivery slice — the terminal revision over the files the uid-1000
	// supervisor actually wrote, and its machine-readable degrade reason
	// ("" healthy).
	FilesRev      string `json:"filesRev,omitempty"`
	FilesDegraded bool   `json:"filesDegraded,omitempty"`
	FilesReason   string `json:"filesReason,omitempty"`
}

SpawnEnvHealth is the spawn-env delivery slice of HealthzResponse. Degraded=true carries a non-empty Reason from the closed reason set (spawn_env_unavailable, spawn_env_unauthorized, spawn_env_no_credential, spawn_env_bad_response); the controller mirrors it into the Workspace CRD's secretsDelivery status.

type StatuszResponse

type StatuszResponse struct {
	Healthy             bool          `json:"healthy"`
	Ready               bool          `json:"ready"`
	Connected           []string      `json:"connected"`
	ProvidersConfigured int           `json:"providers_configured"`
	Sessions            []SessionInfo `json:"sessions"`
	SessionsActive      int           `json:"sessions_active"`
	SessionsError       int           `json:"sessions_error"`
	LastError           string        `json:"last_error"`
	AgentType           string        `json:"agent_type"`
	AgentVersion        string        `json:"agent_version"`
	UptimeSeconds       int           `json:"uptime_seconds"`
	Disk                *DiskUsage    `json:"disk,omitempty"`
	Memory              *MemoryUsage  `json:"memory,omitempty"`
	CPU                 *CPUUsage     `json:"cpu,omitempty"`
	// RelayFreeModels: 0 unknown, 1 ok, 2 degraded (terminal fetch
	// failure this attempt — free-tier routing degraded until a re-arm
	// cycle applies, bounded by the 5m→30m backoff; #901 G8, #910).
	RelayFreeModels int32 `json:"relay_free_models"`
	// InFlightDeliveries: the ledger's unresolved delivery count pod-wide
	// (ledgered + admitted + stalled — the flip gate's drain signal,
	// design 0055 M4; 0 when the authority is not mounted).
	InFlightDeliveries int64 `json:"ledger_in_flight"`
	// OldestBusySeconds: age of the longest-busy session, 0 when idle
	// (D6/#998: unattended-escalation detection input — a session busy
	// this long with sustained watchdog flat-suppressions is likely
	// hung-and-alive; the API escalates to the owner, never executes).
	OldestBusySeconds int            `json:"oldest_busy_seconds"`
	BusyAges          map[string]int `json:"busy_ages,omitempty"`
	Context           *ContextUsage  `json:"context,omitempty"`
	// MemoryPressure is true when memory usage exceeds the warning
	// threshold (85% of cgroup limit). Set by agentd's periodic memory
	// check (US-44.5). The controller reads this to set the
	// WorkspaceConditionMemoryPressure condition.
	MemoryPressure bool `json:"memory_pressure,omitempty"`
	// Warnings mirrors HealthzResponse.Warnings: boot-time degradation
	// notices (ModelResolutionWarningPath). The deep-status scrape feeds
	// the controller's AgentHealthy condition message, so the warning
	// must appear here too or it would be erased between scrapes.
	Warnings []string `json:"warnings,omitempty"`
	// SpawnedRev (US-70.1 / design 0057 I4): the env revision the agent
	// child ACTUALLY spawned with — terminal verification at the point
	// of consumption, relayed from the supervisor's control socket.
	SpawnedRev string `json:"spawned_rev,omitempty"`
	// Degraded (US-70.1 / design 0057 I10): active machine-readable
	// degrade reason ("" healthy). First value of the family:
	// spawn_env_unavailable — the child spawned platform-env-only from
	// the last-good cache because the pull missed its bound.
	Degraded string `json:"degraded,omitempty"`
	// Relay (US-72.4): the relay-only liveness slice (cached monitor
	// state; nil when not wired). Mirrors healthz's field on the deep
	// status endpoint so a relay degrade is visible between scrapes.
	Relay *RelayHealth `json:"relay,omitempty"`
}

StatuszResponse is the response for GET /v1/statusz.

Directories

Path Synopsis
Package secrets materializes user-supplied secrets onto the sandbox pod filesystem with strict validation and TOCTOU-safe permissions.
Package secrets materializes user-supplied secrets onto the sandbox pod filesystem with strict validation and TOCTOU-safe permissions.

Jump to

Keyboard shortcuts

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