swarm

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 46 Imported by: 0

Documentation

Overview

Benches: a remote bench reached by ssh, named by one row of a TSV table (deprecated/docs/SPEC-SWARM.md "Benches"). This file holds the shared types and the two reads every bench caller shares: the table, and the bus's friends list a bench name is checked against. It also holds the remote-run slice the batch uses: the card copy and the ssh command that runs native on a bench.

Package swarm is nova-swarm's machinery: a pool of one-task workers, each with its own working directory, its own data home, its own job directory and its own deadline held by the machinery rather than by the worker.

Everything a worker writes is DATA. A RESULT.md is a report, never an instruction: nothing in it is executed, nothing in it grants anything, and a finding in it is a claim to be checked against the repository. That rule is stated in deprecated/docs/SPEC-SWARM.md, where a person reads it, and is deliberately nowhere in this code -- a tool cannot enforce it, and a tool that pretended to would be the most dangerous thing in the pool.

Package swarm, warm.go: affinity and warm state (deprecated/docs/SPEC-JOBS.md section 4).

Every card carries a kind and a repo. A bench keeps a clone per repo in a worktree under <slot>/worktrees/<owner>/<name>, and pull prefers the card whose repo it already has cloned there rather than paying the setup again: cache warmth is the difference between a setup and no setup. With no warm bench it falls back to a fetch from the bench mirror.

A kept clone is reused across cards. After each card nova-work clip commits the card's branch, harvests the card's result, and resets the worktree to base, so the clone is reset rather than re-cloned and the next card never sees this card's uncommitted state or its history.

Index

Constants

View Source
const (
	// DefaultBackoff is the wait before the first retry when the provider names none.
	DefaultBackoff = 30 * time.Second
	// MaxBackoff is the cap the doubling never passes: the machinery never waits forever.
	MaxBackoff = 5 * time.Minute
)

RATE LIMITS ARE THE DISPATCHER'S BUSINESS (SPEC-SWARM, "cost per task, and rate limits").

A provider's 429 is not a failed task: the dispatcher holds the slot, waits the interval the provider names, or this tool's own --backoff, and retries the SAME task once. A second 429 fails it with rc=429 in its sidecar and its usage row, so `triage` can see that a batch's silence was a limit rather than a set of bad tasks.

View Source
const (
	// DecisionsFile is the per-task decision log beside the pool's outcome.
	DecisionsFile = "decisions.log"
	// DefaultDecideFloor is the confidence floor triage --decide starts at.
	DefaultDecideFloor = 0.9
)
View Source
const (
	// FenceAllow, FenceAsk and FenceDeny are the harness's own permission actions. `ask` in
	// a non-interactive `run` is auto-rejected and the model STOPS -- the whole run ends and
	// the card's commits are stranded. `deny` is a TOOL ERROR returned to the model, which
	// notes it, works inside the job instead, and continues (issue #918). So the fence's
	// fallback is deny, and `ask` is never written: there is no terminal to answer it.
	FenceAllow = "allow"
	FenceAsk   = "ask"
	FenceDeny  = "deny"

	// FenceExternalDirectory is the permission key the harness asks under for a path
	// outside the cwd; FenceWebfetch is the network tool. Neither is left to prompt.
	FenceExternalDirectory = "external_directory"
	FenceWebfetch          = "webfetch"
)

THE HARNESS'S OWN FENCE (issue #644). OpenCode asks before a tool touches a path it calls EXTERNAL, and a `run` with no terminal answers every such question by rejecting it:

permission requested: external_directory (<dir>/*); auto-rejecting
Error: The user rejected permission to use this specific tool call.

The model stops there, publishes nothing, and the batch scored the card `no-result` -- the model's own doing -- when the truth is that the machinery fenced off a path the CARD was told to use. Eight of thirty cards on Rowan's bench died this way on 2026-09-16.

TWO THINGS ARE WRONG AND BOTH ARE THIS FILE'S BUSINESS. The fence is configured by nothing -- the job runs on the harness's defaults, which call everything outside the harness's own cwd external -- and a rejection it makes is invisible to the reader, who sees only the absence of a result.

WHAT IS NAMED AND WHAT IS NOT. The job directory and everything under it, and nothing else -- not the job's `jobs` parent. An earlier draft of this fix named the parent too, on the belief that the fence resolves a card's `../scratch` against the HARNESS's cwd rather than the shell's cwd after `cd repo`; a real run of both binaries against the harness showed it resolving after the `cd`, with no rejection either way, so the parent rule bought nothing and widened a job's fence to every SIBLING job in the same slot on a --no-wall bench. The WALL, not the fence, is what keeps a card inside its job (SPEC-SANDBOX rule 1), and a fence rule is no place to hand one card another's work.

BOTH WILDCARD SPELLINGS ARE WRITTEN, and not because `*` stops at a separator -- it does not: the harness's own matcher turns `*` into `.*`, which crosses `/` freely, so `<job>/*` already admits `<job>/scratch/*`. `<job>/**` is written beside it because it is the spelling a person reads as "everything under here", and a matcher that ever tightened `*` to one segment would leave the rule meaning what it says.

View Source
const (
	MarkerMalformed = "MALFORMED"
	MarkerNoResult  = "NO-RESULT"
	MarkerRev       = "REV"
	CopiedResult    = "RESULT.md"
)

The three names under <pool>/reports/<job>/.

View Source
const (
	MarkWords     = 2
	MarkBareWords = 1
)

MarkWords is how many tokens may come BEFORE the mark and leave it a label.

A SUBSTRING SEARCH IS NOT A LABEL (the swarm delta eye on #150). `strings.Contains(lower, mark)` accepted the word anywhere on the line, so a RESULT.md bullet, a finding row or a heading that QUOTED the provider's sentence beside the word `error` was marked and classed -- the very line the mark rule was added to exclude, and a job classed this way is never retried, so the false mark takes a real 429's retry away.

A harness's label is a stamp, a component name and the mark: `Error: …`, `opencode: error: …`, `2026-09-12T12:24:31Z error: …`, `API Error: 400 …`, `openai.BadRequestError: Error code: 400 …` -- never more than two tokens before it. Prose has more (`- the provider printed error: …` has four), and a quote character before the mark means the line is quoting rather than reporting.

AND AT MOST ONE OF THOSE TOKENS IS A BARE WORD. Two alone is not enough: `the harness error is …` and `| 3 | red | error | …` are a sentence and a table row, and both put a mark two tokens in. A label token is a STAMP (it carries a digit) or a PREFIX (it ends in `:`, `]`, `|` or `>`); anything else is a bare word, and a harness writes at most one of them before its mark -- the `API` of `API Error:`, the `provider` of `provider error:`. A second bare word is prose.

View Source
const (
	AskedResultPrefix = "RESULT: ASKED "
	AskedWrittenBy    = "written-by: nova-swarm native (the card published no report of its own)"
)

AskedResultPrefix opens line 1 of that report, and AskedWrittenBy is the line that says the machinery wrote it. Both are constants because two readers already depend on them: the one that writes the report and the one that refuses to count it as the card's own.

View Source
const (
	Pending = "pending"
	Running = "running"
	Done    = "done"
	Failed  = "failed"
	// Aborted is where a task goes when a PERSON replaces it: `requeue` writes the new task
	// and moves the old one here, so a pool cannot wedge (the new-user audit, F2 and F7:
	// `requeue` added and never removed, pending went 2 to 4, and the only way out of the
	// refusal's own remedy was `rm`).
	Aborted = "aborted"
	// RoutedOut is where a task goes when the ladder rules it judgment work owed to a
	// child or a friend (issue #1486): a card the dispatcher routes to ask-child or
	// ask-bus is NOT dispatched to a model, it is parked here with its ROUTE line, and
	// the coordinator (or a friend, over the bus) takes it from here.
	RoutedOut = "routed-out"
)

The four states a task's files sit in. A task is in exactly one of them, and the move between two of them is a rename, which is the claim: two dispatchers both try it and the kernel picks one.

View Source
const (
	Reports = "reports"
	Scratch = "scratch"
	Slots   = "slots"
	Usage   = "usage"
)

The pool's other directories. reports/ holds the pages and one directory per finalized job; usage/ holds one row per job and is OUTSIDE everything reclaim removes, which is the whole of rule 12.

View Source
const (
	RunLock   = "run.lock"
	SlotsLock = "slots.lock"
	StopFile  = "stop"
)

RunLock excludes dispatchers and nothing else; SlotsLock guards one slot-file transition and is never held across a wait (rule 17).

View Source
const (
	WorkerClassPublic = "public"
	WorkerClassPaid   = "paid"
)

Public class words a worker description may carry. Empty means paid: no description written before this gate changes meaning by being re-read.

View Source
const (
	JobCPURequest       = "1"
	JobMemoryRequest    = "2Gi"
	JobEphemeralRequest = "2Gi"
	JobMemoryLimit      = "2Gi"

	// KubeReservedFloorGiB is the floor kube-reserved/system-reserved holds back.
	KubeReservedFloorGiB = 25
)

The Job's requests and limits: the capacity line as scheduler facts.

View Source
const (
	DefaultWorkerLeaseDur = 30 * time.Minute
	DefaultWorkerImage    = "nova-card:latest"
	DefaultWorkerModel    = "deepseek/deepseek-chat"
)

Default lease duration and container image.

View Source
const (
	// QuarantineDirName is the directory under queue holding all quarantined cards.
	QuarantineDirName = "quarantine"

	// QuarantineMetadataFile is the metadata descriptor inside each quarantined card directory.
	QuarantineMetadataFile = "quarantine.json"

	// ProviderFailedExt is the legacy suffix that caused cards to sit in limbo.
	ProviderFailedExt = ".provider-failed"
)
View Source
const (
	// CardExt is the suffix that makes a file in queue/ a card.
	CardExt = ".card"
	// QueueName is the per-bench directory of waiting cards.
	QueueName = "queue"
	// TakenName is the per-bench directory of cards a worker owns, one
	// <worker>-<name>.card each.
	TakenName = "taken"
	// MirrorTimer is the bench mirror's fetch-only cadence: an idle worker steals
	// from the fullest bench once per five minutes, not once per look.
	MirrorTimer = 5 * time.Minute
)

The per-bench queue's names and cadence, as section 2 prints them.

View Source
const (
	// CacheDirName is the one shared cache directory under a swarm root.
	CacheDirName = "cache"
	// DefaultReapOlder is --older's default: a slot whose newest harness log is older than
	// an hour, with no RESULT.md, is a slot no live job is writing.
	DefaultReapOlder = time.Hour
)

SHARED PER-BENCH CACHES (issue #1048). A native job downloads the Go toolchain and every module into its own sandboxed data home -- up to 5 GB per slot -- and 120 cards filled hulk and vision to 100%. The toolchain and the module cache are the same for every job under one swarm root, so they live once under <root>/cache and every job's child is pointed at them (GOMODCACHE, GOCACHE, NPM_CONFIG_CACHE). The root is a permitted write root beside the job directory (docs/SPEC-SANDBOX.md), never the job's own data home.

View Source
const (
	ClassOK        = "ok"
	ClassClean     = "clean"
	ClassPlanOnly  = "plan-only"
	ClassNoResult  = "no-result"
	ClassMalformed = "malformed"
)

The classes.

View Source
const (
	RouteFallbackNoKey    = "no-key"
	RouteFallbackBelow    = "below-floor"
	RouteFallbackNoModel  = "rung-is-asked-not-run"
	RouteFallbackRefused  = "refused"
	RouteFallbackNoLadder = "no-ladder"
	// RouteFallbackNoAccount is the refusal to ask at all: a call nobody can
	// account for is not made, so the rules answer and today's model stands.
	RouteFallbackNoAccount = "no-accounting"
)

The enumerated reasons a route fell back. They are tokens and not prose, so the log can be counted by them.

View Source
const (
	SlotReserved = "reserved"
	SlotLaunched = "launched"
	SlotOrphaned = "orphaned"
)

The states a slot file passes through.

View Source
const (
	DecideAdopt      = "adopt"
	DecideReclaim    = "reclaim"
	DecideUnknown    = "unknown"
	DecideUnlaunched = "unlaunched"
	DecideQuarantine = "quarantine"
)

The decision words.

View Source
const (
	SlotWeightRead  = 1
	SlotWeightHeavy = 4
)
View Source
const (
	ModelPlaceholder   = "{model}"
	PromptPlaceholder  = "{prompt}"
	BaseURLPlaceholder = "{base_url}"
)

The placeholders a worker description may write into harness_args.

View Source
const (
	// MinExecutionTimeout is the absolute lower bound on execution timeout (5s).
	MinExecutionTimeout = 5 * time.Second

	// MaxExecutionTimeout is the absolute upper bound on execution timeout (2h).
	MaxExecutionTimeout = 2 * time.Hour

	// DefaultCardTimeout is the canonical default timeout for an unconfigured card (30m).
	DefaultCardTimeout = 30 * time.Minute

	// DefaultStepTimeout is the canonical default timeout for an individual step (5m).
	DefaultStepTimeout = 5 * time.Minute

	// DefaultGitTimeout is the default budget for a git/VCS operation (1m).
	DefaultGitTimeout = 1 * time.Minute

	// MaxGitTimeout is the maximum budget allowed for a single git operation (1m).
	MaxGitTimeout = 1 * time.Minute

	// MinGitTimeout is the minimum budget floor for a git operation when time allows (5s).
	MinGitTimeout = 5 * time.Second

	// DefaultGracePeriod is the grace period granted to a process group between SIGTERM
	// and SIGKILL, matching TerminateGrace (3s).
	DefaultGracePeriod = TerminateGrace
)
View Source
const (
	EndDone         = "done"
	EndKilled       = "killed"
	EndBudget       = "budget"
	EndUnverifiable = "budget-unverifiable"
	EndViolation    = "violation"
	EndFailed       = "failed"
	EndUnknown      = "unknown"
	EndLaunchFailed = "launch-failed"
	EndInputLimit   = "input-limit"
	// EndProvider is a launch that did not take: the harness died inside the launch
	// grace with a provider server error in its tail. It is retried with backoff and,
	// after the third fast failure, is filed with the provider's own ref (issue #900).
	EndProvider = "provider"
	// EndWall is a job the harness's own fence stopped at a path outside it, with no
	// RESULT.md: the death names the path and the commits ./repo kept (issue #918). It is
	// also a card the harness's own fence or the OS wall stopped before it could publish:
	// the machinery ended the task, not the model (wall.go, issue #644's follow-up).
	EndWall = "wall"
)

The ways a job ends, as the `end` column spells them.

View Source
const (
	// SandboxRefusedMark is the sandbox's own refusal prefix (cmd/nova-sandbox/main.go).
	SandboxRefusedMark = "SANDBOX REFUSED"
	// OperationNotPermittedMark is the kernel's refusal, which on a path outside the write
	// set is the wall talking (internal/sandbox/gpu.go, the Darwin probe).
	OperationNotPermittedMark = "Operation not permitted"
	// WallStepMark is how a card numbers its steps; the LAST one in the log is where the
	// wall stopped it.
	WallStepMark = "STEP "
)

The wall's own words, kept here beside the classifier that reads them.

View Source
const (
	UsageOpenCode = "opencode"
	UsageNone     = "none"
)

The usage sources a description may declare (rule 13). There are two.

A SOURCE IS NAMED FOR WHAT IT IS. `opencode` promised that OpenCode's own accounting is read, and did not read it: on 2026-09-11 two real jobs burned 61,875 and 85,308 tokens against `--tokens 20000` and both reported `budget=-/20000`, because OpenCode writes `opencode.db` and this tool read a tab-separated file instead. The name is true now -- `opencode` reads that database through `sqlite3`, read-only, in the data home this tool exported for the job -- and the tab-separated file is not a name a description may carry: a caller names a SOURCE, never a file some harness might write.

View Source
const AskedResultName = BlockedResultName

AskedResultName is the report a run writes FOR a card that ended by asking. It is `RESULT.md` for the same reason the blocked report is: that is the one file every gather already looks for, and a card that ended by asking published nothing there.

View Source
const AskedTailBytes = 64 << 10

AskedTailBytes bounds what the question test reads of a capture. A card's capture is a whole transcript and can be megabytes; the question is the LAST thing in it, and no reader needs a megabyte read to answer a yes/no.

View Source
const BlockedResultName = "RESULT.md"

BlockedResultName is the report a run writes FOR a card that never published one, so the coordinator's harvest reads a named end instead of an absence. It is `RESULT.md` because that is the one file every gather already looks for, and its first line carries no findings head, so it can be scored `plan-only` and NEVER `ok` or `clean`: a report the machinery wrote can never be counted as work a worker did.

View Source
const CardContractWanted = "" /* 266-byte string literal not displayed */

CardContractWanted is what `result-first` wants, in ONE form, for the remedy line and for any tool that has to print the rule rather than apply it. It names the stopgap in the same breath so a writer reading a card `cut` wrote is not told it is wrong, and so that nobody has to read two documents to learn which of the two to type.

View Source
const CardDependsRemedy = "DEPENDS-ON: <card-id>[, ...] or DEPENDS-ON: -"

CardDependsRemedy is what the `depends-on` drift names. One remedy for the token, the two forms the key is allowed to take.

View Source
const CardOutEnv = "NOVA_CARD_OUT"

CardOutEnv names the card wrapper's out directory in a harness's environment.

View Source
const CardParentPathWanted = "" /* 385-byte string literal not displayed */

CardParentPathWanted is what `no-parent-path` wants, in one line, for the remedy and for any tool that prints the rule rather than applies it.

View Source
const CardReadPathsMarker = "READ:"

CardReadPathsMarker is the card line this reads. A card that needs a read-only path outside its job says so in one line -- `READ: /sys/kernel/security/lsm` -- and that line, and nothing inferred from the card's prose, is what opens the fence for it.

View Source
const CardTestGateRemedy = "" /* 276-byte string literal not displayed */

CardTestGateRemedy is what the `test-gate` token wants, in one line, in the same table shape as `depends-on` (CardDependsRemedy). It is not merged into the typed-header remedies map on purpose -- that map's size is pinned by the `--rules` listing and the cardLintChecks count in deprecated/cmd/nova-swarm/lint.go, which is the wiring this card's PATHS does not reach.

View Source
const Dash = "-"

Dash is the absence this file writes, and never a zero.

View Source
const DefaultContractLines = 20

DefaultContractLines is the bound on evidence lines when none is named.

View Source
const DefaultLaunchGrace = 15 * time.Second

DefaultLaunchGrace is how long a harness may run before its exit stops counting as a launch failure. A worker description's `launch_grace` overrides it.

View Source
const DefaultLaunchRow = "*"

DefaultLaunchRow names the providers-table row a route launches with when its provider has no row of its own. It is declared in the table like every other row, so a route the table does not name still launches through the one launcher and its one argv shape -- never through a guessed argv or a per-provider script.

View Source
const DefaultLaunchTimeout = 10 * time.Second

DefaultLaunchTimeout is how long the runner waits for a supervisor to identify itself. A tool property like a timeout, not a fact about anybody's pool.

View Source
const DefaultNativeIdle = 300 * time.Second

DefaultNativeIdle is the window a native run gives a card that is saying nothing and spending no CPU. It is 300 seconds, which is `batch --idle`'s own default: one number for the two verbs, so a card does not mean two different things on two paths. It is not a provider read deadline. A response whose headers arrived and then sent no body bytes is UNKNOWN, and that deadline is the provider proxy's body timer, not this window. Whole-card silence is not that evidence: a quiet think looks the same, and it stays CARD IDLE.

View Source
const DefaultPullFloor = 0.9

DefaultPullFloor is the confidence floor behind which the scorer's order is a suggestion, never an authorization.

View Source
const DefaultRouteFloor = 0.9

DefaultRouteFloor is the confidence floor the dispatch route starts at. It is the adoption default of SPEC-DECIDE rule 5 and it is stated HERE, beside the call site, because a floor belongs to the call it gates.

View Source
const DefaultStageTimeout = 120 * time.Second

DefaultStageTimeout is the hard timeout for card staging (120 s).

View Source
const DefaultUsageInterval = 5 * time.Second

DefaultUsageInterval is how often a live sample reads the source. It is a tool property, like a timeout, and never a fact about anybody's job. Rule 13 names it for `run` and rule 13d gives `native` the same flag and the same default.

View Source
const (
	FenceRejectionMark = "permission requested: "
)

FenceRejectionMark and fenceRejectionTail are the harness's own words, printed by its `run` command for every permission it answers with a reject:

! permission requested: external_directory (/x/y/*); auto-rejecting

The line carries the harness's colour codes around it, so it is found by its two marks and never by an exact match on a whole line.

View Source
const GateOutBytes = 400

GateOutBytes bounds what one command's verbatim output contributes to the gate section. `go test` can print screens on a red package; the reader needs the verdict line and the first of the hurt, and the body has a budget like any other.

View Source
const GateSectionBytes = 4096

GateSectionBytes is the PR-body budget the whole gate section is bounded to when the caller names none: the same 4096 the harvest bounds a body to.

View Source
const GateTimeout = 10 * time.Minute

GateTimeout bounds one gate command. The gate runs the touched packages only, and the per-package time budget answers in two minutes at most; ten is the outer bound that keeps a hung bench from hanging the harvest with it.

View Source
const GroupDrainWait = 300 * time.Millisecond

GroupDrainWait is how long the check above gives a job's group to empty. It is a tool property, not a fact about anybody's job: long enough for a wrapper's exit to land after the work's, short enough that it is invisible beside a launch.

View Source
const HarvestGateHeading = "## Harvest gate"

HarvestGateHeading is the fixed PR-body heading the harvest's gate line is written under, the one heading a reader may trust. Everything under it is the harvest's own: the section is stripped and rewritten on every append, so a card-typed copy of the heading never survives a harvest pass (issue #2386: "a card's self-reported gate is data, never a gate").

View Source
const JobLeaseHeartbeat = 30 * time.Second

JobLeaseHeartbeat is how often a held lease's mtime is bumped. It is far under the ten minutes the reaper allows a heartbeat to age, so a bench under load that misses a tick or two still reads as live.

View Source
const JobLeaseName = ".lease"

JobLeaseName is the lease file's name inside the job directory. The reaper knows it by this name and by nothing else.

View Source
const JobLeaseStale = 10 * time.Minute

JobLeaseStale is how old a heartbeat may be before a lease whose OWNER CANNOT BE ASKED ABOUT stops counting as held. It is the reaper's own window (scripts/bench-hygiene.sh, HYGIENE_LEASE_STALE_MIN=10), read the same way and for the same reason: it is the only word there is about a record naming no pid this kernel can answer for. A lease whose pid this host CAN be asked about never reaches it.

View Source
const JobRepo = "repo"

JobRepo is the clone under a job directory: <job>/repo, the same name gather and harvest already look for.

View Source
const LaunchSchema = "nova.swarm.launch/1"
View Source
const LiveSampleLimit = 5 * time.Second

LiveSampleLimit is what ONE live sample is given, whatever the interval (SPEC-SWARM rule 13d): "a read is given 5 seconds whatever the interval, no sample starts while one is unanswered, a read still unanswered at its limit is abandoned and counted as a failed read". It is a fixed number rather than a function of the interval because at a short interval three slow reads would end an honest card `budget-unverifiable`.

View Source
const MaxLaunchRecordBytes = 65536
View Source
const MaxProviderAttempts = 3

MaxProviderAttempts is the whole number of launches one task gets: the first, then the two retries the grace earns.

View Source
const MaxRegularRecord = 16 * 1024 * 1024

MaxRegularRecord is the maximum size of a worker-written record read whole into memory by the supervisor. Normal records (RESULT.md, harness.log, sidecars, task files, retry state) range from tens of bytes to a few MiB for verbose test runs; 16 MiB provides generous margin while bounding supervisor memory consumption against unbounded reads or rogue worker files (security#30, finding 4 residue, issue #234).

View Source
const NoBatchTokensRefusal = "" /* 294-byte string literal not displayed */

NoBatchTokensRefusal is the ONE line a batch of cards prints when it was given no budget word (SPEC-SWARM rule 13d, issue #1545). Like NoSlotsStoreRefusal beside it, it is a constant so that the batch's refusal and `native`'s own say the same thing and a caller who greps for one finds the other.

IT NAMES BOTH SPELLINGS, because a caller running a local model has no accounting to read and `unmetered` is the only honest thing they can say; and it says whose budget the word is, because the first question a reader asks of a batch-wide flag is whether ten cards share it.

View Source
const NoIdleWindow = time.Duration(0)

NoIdleWindow is the window `--idle 0` reaches WatchIdle as: no watch at all, which is the behaviour every native run had before this file existed. It is named because a bare zero beside a duration reads like an oversight, and this one is a choice a caller can type.

View Source
const NoSlotsStoreRefusal = "" /* 163-byte string literal not displayed */

NoSlotsStoreRefusal is the ONE line printed when a native launch was asked for without a bench slot store or without an owner (nova-tools#1546). It lives here, beside the lease code, because FOUR places must print the same sentence -- `nova-swarm native` itself, the batch that refuses before any card runs, and the two paths that build a native argv -- and a remedy that drifts between them is a remedy a reader stops trusting.

It names the store, the owner AND the exact command that makes a one-seat store, because "pass --slots-store <dir>" on a bench that has never had one is not a remedy, it is a second question.

View Source
const ONoFollow = oNoFollow

ONoFollow is oNoFollow for callers outside this package: `native` opens a log inside the JOB, which is the worker's own writable directory, and a symlink planted there by a card would carry that write out of the wall (security#30's class, issue #608).

View Source
const OpenCodeDB = "opencode/opencode.db"

OpenCodeDB is the database's path inside the job's data home (XDG_DATA_HOME).

View Source
const PermissionDeniedMark = "Permission denied"

PermissionDeniedMark is the kernel's refusal on linux. landlock denies a write outside the write set with EACCES, and the C library spells that `Permission denied` -- so the marks this package had (`SANDBOX REFUSED`, `Operation not permitted`) named NO linux refusal at all. Measured inside the swarm's own wall on hulk: `sh: 1: cannot create /tmp/nova-wall-probe-swarm: Permission denied`. Like OperationNotPermittedMark it counts only ON A PATH: `Permission denied` with no path in the line is some other permission, and a card's own test output is full of sentences.

View Source
const PhraseFloor = 12

PhraseFloor is the shortest a provider's sentence may be, in characters, and it is a FLOOR because a phrase is a knife: a job classed `input-limit` is a job that is never retried.

View Source
const PoolIdentityRemedy = "remedy: make -C fleet converge (rowan-tools) writes the pool identity.tsv"

PoolIdentityRemedy is the one remedy every pool identity refusal names (nova-tools #3193). The pool identity is bench configuration: the fleet converge in rowan-tools writes <pool>/identity.tsv (its pool_identity task), so a bench refusing for it is an unconverged bench, never a hand-written file. The card wrapper carries the refusal line, this remedy included, onto the card record as its why.

View Source
const PrewarmReceiptDir = "prewarm"
View Source
const ProfileCatalogMax = 262144

ProfileCatalogMax is the catalog read ceiling from SPEC-SWARM-PROFILES: at most 262144 bytes, plus the overflow-detection byte a reader uses to prove it is not truncating.

View Source
const ProfileExitCode = 2

ProfileExitCode is the exit status a refused profile load carries: admission refusals are exit 2 before any worker starts (SPEC-SWARM-PROFILES, refusals and admission bounds).

View Source
const ProviderBeatName = ".provider-beat"

ProviderBeatName is the file the supervisor touches each time it observes provider bytes. The lease heartbeat only renews when this file has moved, so a hung socket lets the lease expire rather than keeping it alive on a timer.

View Source
const ProviderBodySilence = 45 * time.Second

ProviderBodySilence is how long the response body may sit with no bytes after headers have arrived. The proxy enforces it. A stream that keeps sending is not cut off. Silence past this is UNKNOWN and the card is not launched again. It is under the 90s a black-holed attempt may cost.

View Source
const ProviderChunkTimeout = 45 * time.Second

ProviderChunkTimeout is written into the job config as chunkTimeout, in milliseconds. Same limit as the header option, and the same non-claim: setting it was measured not to end the stall.

View Source
const ProviderClass5xx = "PROVIDER-5XX"

ProviderClass5xx is the class word the hand-back line carries in place of INCOMPLETE.

View Source
const ProviderHeaderTimeout = 45 * time.Second

ProviderHeaderTimeout is written into the job config as headerTimeout, in milliseconds. It is not the body-read deadline. It stays under 90s so a harness that does honor it cannot outlive the body-read budget.

View Source
const RoutesEnv = "NOVA_SWARM_ROUTES"

RoutesEnv names the ordered route list (provider/model, comma or space separated) the launcher hands the run, so the run can name the route after the one that failed.

View Source
const SQLiteBinary = "sqlite3"

SQLiteBinary is the one program a usage source needs on PATH.

View Source
const SandboxBinary = "nova-sandbox"

SandboxBinary is the name this tool looks for on PATH when --sandbox names no path. It is the tool's OWN name and not a path: a default PATH lookup of a named command is what this tool already does for the harness, and no directory is guessed.

View Source
const SandboxNoneByFlag = "none-by-flag"

SandboxNoneByFlag is the name the OK line carries for a run the caller unwalled with --no-wall: a run that had no sandbox, told apart here from one that had a real one, so no refusal attributes a denial to a wall that was not there.

View Source
const ShareMemoryHeadroom = 2

ShareMemoryHeadroom is how many times the measured per-card RSS a card is planned at before the divide. A card measured at ~600 MB is budgeted ~1.2 GB, so a spike in one card does not spill into its neighbour and the OS keeps a cushion of its own rather than the whole box being priced at the measured minimum.

View Source
const SizeHeadroomFactor = 1.5

SizeHeadroomFactor is the launch headroom per tick: cores x 1.5 - load.

View Source
const SizeLoadFactor = 1.25

SizeLoadFactor is rule (a): the one-minute load holds at most 1.25 x cores.

View Source
const SizeScaleFactor = 1.5

SizeScaleFactor is rule (b): cards per minute at W holds at least 1.5 x cards per minute at W/2.

View Source
const SlotLeaseName = JobLeaseName

SlotLeaseName is where the slot lease lives: <slot>/.lease, beside jobs/ and data/.

View Source
const SlotStoreLockName = "slots.lock"

SlotStoreLockName is the bench store's one lock file, beside shares.tsv and the slots/ directory. It is NOT part of the store's format in the sense that matters: shares.tsv keeps every byte of its shape, `slots list` still reads directories, and a store made by an older `slots init` grows this file the first time a take runs against it.

View Source
const SlotStoreWait = 10 * time.Second

SlotStoreWait is how long a take or a release waits for the store lock. Every holder does a bounded reap, a count and at most `--n` mkdirs and then releases; a wait longer than this is a holder that is stuck, not a bench that is busy.

View Source
const SlotsWait = 10 * time.Second

SlotsWait is how long a slot transition waits for the lock. It is short because every holder of it does one read, one compare and one rename and then releases: a wait longer than this is a holder that is waiting on something, which this lock forbids.

View Source
const StartupCostFile = "startup-cost.tsv"

StartupCostFile is the measurement's name under a pool's or a native run's root.

View Source
const SteadyWindow = 2 * time.Second

SteadyWindow is how long a read or a rename waits out somebody else at the same path. It is a property of the FILESYSTEM's replace window -- the microseconds a delete is pending -- and not of anybody's job, so it is short, and long enough that a scheduler that parks this goroutine for a quantum still lands inside it.

View Source
const TerminateGrace = 3 * time.Second

TerminateGrace is how long a group is given to stop after the terminate and before the kill. Long enough that a harness can flush what it was writing, short enough that a reap is a reap.

View Source
const TimelineFileName = "timeline.tsv"

TimelineFileName is the per-turn timeline a native run writes beside a card's RESULT.md and usage.tsv. It is one header line and one row per model turn and per tool call.

View Source
const TriageStateFile = "triage.json"

TriageStateFile is the consumed set's file name.

View Source
const UsageIntervalFloor = time.Second

UsageIntervalFloor is the shortest interval `native` accepts. Rule 13d: an interval under one second "could end an honest card on three quick reads" -- three failed reads in a row end a card `budget-unverifiable`, and at a tenth of a second that is a third of a second of bad luck rather than a source that has really stopped answering.

View Source
const WorkerCap = 64

WorkerCap is the ceiling on --workers (Glenn, 2026-09-10). A request above it is a REFUSAL and not a silent clamp: the prototype prints a note and clamps, and a caller who asked for 200 workers has a belief about throughput that a note at the top of a log does not correct.

Variables

View Source
var ApprovedCommands = []string{
	"cat", "diff", "git", "go", "gofmt", "grep", "head", "ls", "make", "rg", "wc",
}

ApprovedCommands is the closed harness-prompt allowlist. A first-token not in it is denied without prompting, so a card does not spend a turn on a permission refusal. This is not command confinement and not the OS wall: git, go and make can still invoke other programs. curl, wget, ssh, nova-sandbox, nova-secrets and a login shell are not in it. No new permissions.

View Source
var BudgetColumns = []string{"tokens_in", "tokens_out", "reasoning"}

BudgetColumns are the columns rule 13's budget counts: what the job SENT, what it GENERATED, and what it REASONED. Cache reads are the provider re-reading context it already holds, and cache writes are that context being laid down once; neither is work this job asked for, and counting them ended a real four-million-token job at 4.18M in under three minutes with zero findings (dogfood D6, 2026-09-11). The usage ROW keeps all five columns -- `cost` reads what it always read -- the BUDGET counts these three.

View Source
var CardBaseRemedies = map[string]string{
	"paths-at-base":      "every PATHS entry names a file, directory or glob that exists at the card's base-sha (or is a new `_test` file); cut the card from the tree at that sha, not from the issue's words, and hand the lint a repository holding the sha with `--repo <dir>` (failed-cards-2026-09-22 class 9, nx-f19)",
	"no-push-steps":      "a card ends at a local commit: no STEP runs `git push` or `gh`, because the wall holds no credential and the harvest pushes and comments; say `no gh, no push` in RULES, never as a STEP command (failed-cards-2026-09-22 class 8, holdfix and nx-r repair cards)",
	"leg-in-fleet":       "LEG: (or each LEGS: entry) is a leg the fleet's leg table carries -- `sbcl`, not `lisp` -- and the table is handed over with `--legs <file>` (failed-cards-2026-09-22 LEG row, #2728)",
	"donewhen-test-name": "DONE-WHEN: names the runner and a literal test that does not exist at the card's base-sha -- `go test ./<pkg> -run <TestName>`, `pytest <file>::<test_name>` (or `-k <test_name>`), `cargo test <name>` -- so the test can be red on base and green at head; an English outcome (\"applied cleanly\", \"make preflight\") or a runner with no test named is not a control, and the repository holding base-sha is handed over with `--repo <dir>` (#3083, the-control-is-the-sentence)",
	"deadline-p95":       "DEADLINE: is at or above the measured p95 wall of the card's KIND, in seconds (or `finish within <n> minutes`), and the p95 table is handed over with `--p95 <file>`; a kind with no row needs a `*` row or a measurement first (failed-cards-2026-09-22 class 11)",
}

CardBaseRemedies is what each base token wants, in the table shape of CardHeaderRemedies, so `nova-swarm lint --rules` prints them beside the rest.

View Source
var CardContractPrefixes = []string{"RESULT: ", "RESULT "}

THE CONTRACT LINE HAS ONE FORM, AND THIS IS THE ONE PLACE THAT SAYS SO (issue #1741, SPEC-TOOLWORK.md §5 rule 7).

Line 1 of a card is its contract: the swarm hashes everything below it, records the line at admission, and `gather` refuses a `RESULT.md` whose line 1 differs (deprecated/docs/spec-pulse/10-the-card-as-cut-writes-it.md:12-14). Three readers touch it and two of them disagreed on one character:

  • `cut` writes and accepts `RESULT <label> sha=<sha12>` -- no colon (internal/pulse/cut.go, and the example at deprecated/docs/spec-pulse/10-the-card-as-cut-writes-it.md:4);
  • `lint --card`'s `result-first` wanted `RESULT: ` -- with one (deprecated/docs/WORKER-CARDS.md practice 1, and deprecated/docs/SPEC-SWARM.md:969,976);
  • `gather` compares line 1 to line 1 and imposes no prefix of its own, so it follows whichever the other two settle on (internal/pulse/harvest.go, classifyResult).

The cost of the disagreement: every card `cut` writes drew a `result-first` drift, and six cards written by hand on 2026-09-19 each drew one for a colon.

RULED: THE COLON FORM WINS (Rowan, 2026-09-19, on the cold read of PR #1759; docs/SPEC-TOOLWORK.md §5 rule 7). It is SPEC-SWARM's own law and it is what the majority of writers already write -- `cut --kind` (internal/pulse/cutkind.go), `internal/pulse/manager.go`, `internal/worklang/expand.go`. `RESULT: <label> sha=<sha12>` is the form to WRITE.

THE NO-COLON FORM IS ACCEPTED AS A STOPGAP, NOT AS A SECOND RULE. The one renderer still on it is the plain `cut` template path (`internal/pulse/cut.go`), and the follow-up card named in rule 7 rewrites it and adds the class test `every-writer-and-reader-agrees-on-the-result-line`. Until that card lands, refusing the no-colon form would refuse cards a tool on dev writes today, so both are read -- and it is that class test, never a judgement here, that retires the second entry below. internal/swarm/lintcontract_test.go goes red the day any of the three readers changes form.

The colon form is FIRST in this slice, and it is the only form any message names as what to write.

View Source
var CardHeaderRemedies = map[string]string{
	"kind-declared":  "the card carries `KIND: <kind>` as the first typed line under the contract line, and the kind is one `nova-pulse accept --kinds` names; `cut` writes it from the pool row and a model never does (SPEC-TOOLWORK.md §5 rules 1, 3)",
	"paths-declared": "the card carries `PATHS: <glob>[, <glob>...]`, repository-relative, every glob holding at least one literal segment and none of them climbing with `..`; a card that changes nothing says `PATHS: none` (SPEC-TOOLWORK.md §5 rules 1, 2)",
	"test-named":     "the card carries `TEST: [-tags <tags>] <package> <TestName>` -- the package repository-relative and the name a Go test name -- or `TEST: none <why>` where the kind declares no gate (SPEC-TOOLWORK.md §5 rule 1; the grammar is cardhdr.ParseTest's)",
	"paused":         "the coordinator paused this kind, so `cut` cuts no card of it and a card launched before the pause is `ACCEPT ABSTAIN reason=paused` at harvest; the remedy is not a rerun but `nova-pulse trust --set trial --queue <dir> --kind <kind> --who <name> --reason <text>` (SPEC-TOOLWORK.md §5 rule 1, eligibility rule 3, §1's abstain list)",
}

CardHeaderRemedies is what each of the four tokens wants, in one line, in the same table shape the twelve older tokens use: a check without a remedy costs a card writer a guess per drift (#1464), and `nova-swarm lint --rules` prints these beside them.

View Source
var CardUsageColumns = []string{
	"job", "attempt", "started", "ended", "end", "rc", "provider", "model",
	"tokens_in", "tokens_out", "cache_write", "cache_read", "reasoning", "usd",
}

CardUsageColumns are the fourteen columns of one card's usage.tsv, in this order. The order is the contract between the native run that writes it and the batch that sums it.

`end` JOINED THEM WITH SPEC-SWARM RULE 13d (issue #1545). The word was being computed by every native launch and thrown away: `writeNativeUsage` has set `row["end"]` since issue #644's follow-up, and there was no column to put it in, so `end=wall` for a card the wall stopped -- and now `end=budget` for one a budget stopped -- reached no reader at all. Rule 13d requires it by name: "The launch a budget ended has `end=budget` in its row", and demanded test 13d reads it there.

IT IS INSERTED WHERE RULE 12 PUTS IT, after `ended` and before `rc`, so that a person who knows the pool's sixteen-column file reads this one without relearning it. THAT IS SAFE FOR OLD READS, and it was checked before it was done: every reader of this file in this repo maps its columns BY HEADER NAME and says so in terms -- `cmd/nova-tokens`'s `readCardFile` ("mapping its columns by the header so the reader never depends on a fixed index"), `internal/pulse`'s `parseProgressUsage` and `status`'s own index. A file written before this change has thirteen columns and no `end`, and every one of those readers answers the empty string for it, which is what an absence is.

View Source
var ErrInvalidTimeout = errors.New("timeout outside valid bounds [5s, 2h]")

ErrInvalidTimeout indicates a duration violates the clamping bounds.

View Source
var ErrNoCard = errors.New("queue is empty")

ErrNoCard is what a pull returns when the bench store's queue/ holds nothing to take. It is not a refusal: an empty queue is an idle puller.

View Source
var ErrNoSQLite = errors.New("USAGE REFUSED reason=no_sqlite")

ErrNoSQLite is the class of a usage source whose one program is not on PATH. It carries the literal refusal line so every caller that surfaces the error says the same thing and a missing reader is never a dash nobody explained.

View Source
var ErrStageTimeout = errors.New("stage-timeout")

ErrStageTimeout is returned when card staging exceeds the hard timeout.

View Source
var InputLimitPhrases = []string{
	"input token limit exceeded",
	"prompt is too long",
	"maximum context length",
	"context length exceeded",
	"reduce the length of the messages",
	"input length and `max_tokens` exceed",
	"request too large",
}

InputLimitPhrases is the table this detection reads, and it is a TABLE because every provider says this in its own words and the tool meets a new provider every time a worker description changes. A description may name more (`input_limit_phrases`), and what it names is ADDED to these rather than substituted for them: a caller teaching the tool one provider's sentence cannot silently un-teach it another's.

EVERY PHRASE IS REFUSAL-SHAPED, in a provider's own words. The lesson is `refusalMarks` (worker.go, dogfood D13): a harness log is a TRANSCRIPT -- the diff the worker read, the spec it quoted, its own prose -- and the bare words `input limit` or `context window` appear in a clean read of any spec that discusses them. A diagnosis that fires on the word for the thing is noise in the one field a reader was told to trust.

View Source
var ProviderErrorMarks = []string{"error", "err", "fatal", "exception", "rejected"}

ProviderErrorMarks are how a harness says THIS IS THE PROVIDER TALKING, and a phrase counts only on a line whose own LABEL is one of them, or directly under such a line.

THE PHRASE ALONE IS NOT ENOUGH, because a harness log is a TRANSCRIPT and the sentences in the table above now live in this repository's own source, README and spec: a worker reading nova-tools quotes `input token limit exceeded` in its RESULT.md, and if it then died of a real 429 the whole transcript search would call that death an input limit and refuse it the retry a 429 has earned (Fable's read of #150, finding 3). It is the D13 rule one file over: a diagnosis that fires on the word for the thing, wherever it appears, is noise in the one field a reader was told to trust. Every real specimen carries a mark -- OpenCode's `Error: `, Anthropic's `API Error: 400 {"type":"error"...}`, OpenAI's `Error code: 400 - {'error': ...}` -- and a harness that puts the mark on its own line above the message is why the line ABOVE counts too.

View Source
var ProviderRetryDelay = func(failed int) time.Duration {
	if v := strings.TrimSpace(os.Getenv("NOVA_SWARM_PROVIDER_BACKOFF")); v != "" {
		if d, err := time.ParseDuration(v); err == nil && d >= 0 {
			return d
		}
	}
	lo, hi := 5, 20
	if failed >= 2 {
		lo, hi = 30, 60
	}
	return time.Duration(lo+randBelow(hi-lo+1)) * time.Second
}

ProviderRetryDelay is the wait before the retry of a launch that just failed: 5-20s jittered after the first fast failure, 30-60s after the second. The jitter keeps a fleet that died on one provider wobble from retrying in lockstep. NOVA_SWARM_PROVIDER_BACKOFF pins the delay for a test.

View Source
var ResultLintRemedies = map[string]string{
	"line2-grammar":        "line 2 is exactly one of `DONE`, `ABSTAIN <why>`, `BLOCKED <why>`, `RED <line>`, `GAP <why>` or `CONFORMS <where>`, at column 0, on its own line",
	"template-copied":      "line 2 holds the template's `<-`: write the ONE disposition you reached, not the template line that lists them; a copied `DONE <- or: ...` is never counted as DONE",
	"blocked-head-is-base": "`BLOCKED head=<base-sha>` says only that head is the base, which is the expected state of a card that has not committed yet; do the card's work, or name the actual obstacle after BLOCKED",
}

ResultLintRemedies is every check this lint makes, with what the writer should have written. One table: a check without a remedy is caught by the test.

View Source
var TimelineColumns = []string{
	"t_start", "t_end", "tool", "wall_ms", "input_tokens", "output_tokens",
}

TimelineColumns are the six columns of one card's timeline.tsv, in this order: the span's two ends, what ran, its wall in milliseconds, and the turn's token counts (empty where the harness reported none).

View Source
var TokenColumns = []string{"tokens_in", "tokens_out", "cache_write", "cache_read", "reasoning"}

TokenColumns are nova-tokens's five types, which are the five this tool sums.

View Source
var UsageColumns = []string{
	"job", "attempt", "from", "started", "ended", "end", "rc", "provider", "model", "repo",
	"tokens_in", "tokens_out", "cache_write", "cache_read", "reasoning", "usd",
}

UsageColumns are the sixteen columns, in this order. The order is the contract.

Functions

func ASDFOutputTranslations

func ASDFOutputTranslations(root, sourceRoot string) string

ASDFOutputTranslations points compiled Lisp output at the bench-shared cache without sharing the harness's whole XDG cache or its per-card HOME.

func AbortedPath

func AbortedPath(jobDir string) string

func AbsResolved

func AbsResolved(path string) (string, error)

AbsResolved is a path made absolute and then symlink-resolved: the one spelling admission records for a slot or a root, so every later compare -- the wall's argv, a containment check, a test's expectation -- is against the same name the filesystem itself uses.

Absolute alone is not enough. On darwin `t.TempDir()` and `$TMPDIR` are handed out under `/var`, which IS a symlink to `/private/var`, so `filepath.Abs` of a relative spelling resolves it (the cwd `os.Getwd` returns is already resolved) while an absolute spelling passes through unresolved -- one directory under two names, and a run's own paths disagreeing with each other depending on how the caller typed them (issue #578).

A path that does not exist YET -- the slot a run is about to create -- cannot be resolved whole, so its deepest existing ancestor is resolved and the rest of the spelling is kept. That is the same answer the path will have once it is made, short of a symlink introduced later inside it.

func AcceptanceUnknown

func AcceptanceUnknown(job string) bool

AcceptanceUnknown reports that this job's provider read may have been accepted and then lost. The marker file is the record. A marker that cannot be read is the same hold: a missing record must not become an ordinary retry. The runner's own verdict line is the second record, for the run whose marker could not be written.

func Admission

func Admission(line, running, queued int) int

Admission is how many workers a bench with this capacity line and this many already on it may still start, never more than the cards waiting and never negative. A full bench admits nothing.

func AdmissionLine

func AdmissionLine(cores, load1, freeGB, memFreeGB int) int

AdmissionLine is section 7's capacity line, in whole workers and whole numbers. It is section 2's CapacityLine (queue.go) read on integer probe numbers rather than measured floats, and it is named apart so neither section borrows the other's rounding: the number of workers nova-swarm pull may start on a bench, min(cores*1.5 - load1, (free_gb-25)/2, memfree_gb/2), floored at zero so a loaded, full or memory-starved bench admits nothing rather than a negative count.

func AdmitsCommand

func AdmitsCommand(line string) bool

AdmitsCommand reports whether a harness bash line is on the harness-prompt allowlist. It is not command confinement and not the OS wall: compound lines (`;`, `&&`, `||`, `|`, backtick, `$()`, newline) are refused, and a first token containing `/` is refused, so `git status; curl` and `/usr/bin/git` do not pass as `git`. Background `&` and process substitutions further show this is not a shell parser; the hint is explicitly non-authoritative.

func AdmitsFetch

func AdmitsFetch(string) bool

AdmitsFetch reports whether a webfetch is in the wall. It is never in the wall: S7 is no web. The argument is the URL a test names.

func AfterGateFailure

func AfterGateFailure(u decide.Unit, rung, reason string) decide.Unit

AfterGateFailure is the escalation rule (Glenn, 2026-09-18: "if we fail then we automatically promote up the intelligence ladder"). A card that failed its gate re-enters the SAME decision carrying that failure as CONFIRMED evidence, and the ladder does the rest: the rung that failed is out of the eligible set and so is its lineage at that height, so the answer is another lineage on the same rung where there is one -- sideways before up -- and the rung above where there is not. It is never a retry on the rung that failed.

The reason travels in the unit and never to a provider: the public projection carries an attempt COUNT and nothing else.

func Alive

func Alive(pid int, started string) bool

Alive reports whether a pid names a live process. Signal 0 asks the kernel and sends nothing; EPERM is a live process this user may not signal, which is still alive.

`started` is the identity the caller recorded for that pid. It is WINDOWS's business, where a pid is re-issued the instant its holder ends; here rule 17's own comparison (slot.go) is the place a start stamp is read, and this call ignores it.

func AppendCardUsage

func AppendCardUsage(path string, row UsageRow) error

AppendCardUsage appends one attempt's usage row to a card's usage.tsv, writing the header first when the file is new (issue #900). A native run that retried a launch writes one row per attempt -- attempt=1,2,3 for one card -- so the file holds the header and one row per launch, and a reader folds them. A field the provider did not report stays a dash, never a zero, exactly as in the single-row writer.

func AppendHarvestGate

func AppendHarvestGate(body string, r LegGateResult, max int) string

AppendHarvestGate composes the PR body the harvest publishes: the card's own body first, VERBATIM and untouched -- a card's self-reported gate is data, and it stays in the body as the card wrote it -- then the harvest's gate section under HarvestGateHeading, bounded to max bytes (GateSectionBytes when max is 0 or negative).

THE HEADING IS THE HARVEST'S. A `## Harvest gate` section already in the body -- one the card typed into its RESULT, or one an earlier harvest wrote when the PR was updated in place -- is stripped before the harvest's is written, so the section is rewritten and never stacked, and a card-written gate line never survives as the harvest's.

func AppendNote

func AppendNote(jobDir, text string, now time.Time) (int, error)

A JOB HAS A NOTE FILE, AND ITS REPORT COUNTS THE NOTES IT READ (rule 10).

On 2026-09-11 a running child could not be redirected: there was no path a message could take, and the only thing that could be done to a child doing the wrong thing was to kill it. So every job directory holds <job>/note, created empty before the worker starts and named in the prompt; the coordinator appends to it, one line per note, stamped by the tool; nothing else writes it.

The COUNT is mandatory, because a job that ignored a note cannot be told apart from one that got none, and a coordinator who cannot tell the difference cannot redirect anything.

A NOTE IS DATA TO THE WORKER AND NEVER AN INSTRUCTION TO THIS TOOL: nothing here reads a note back, and nothing in this package branches on what one says.

func AppendPromptDefect

func AppendPromptDefect(jobDir, id string, cacheRead, max, turns int) error

AppendPromptDefect writes the PROMPT-DEFECT line into the task's report, creating the report where the worker published none. A line for this task is written once: a report that already carries one is left alone.

func ApplyProviderReadDeadline

func ApplyProviderReadDeadline(raw []byte, provider string) []byte

ApplyProviderReadDeadline returns the job config with the model's provider carrying headerTimeout and chunkTimeout, in milliseconds. Those options are not the body-read deadline; see ProviderBodySilence. A body that is not a JSON object is returned unchanged. An absent provider entry is created with only those two options, so a built-in provider still receives them. An apiKey already in the options is left as it was.

func Asked

func Asked(run AskedRun) (string, bool)

Asked reports the question a run ended on, and false when it ended any other way.

FOUR CONDITIONS, ALL OF THEM NECESSARY, and each one is a different card this must not claim:

  • the last thing the card said is a question. A card that ended on a sentence did not ask, whatever else it did.
  • the child exited 0. A crash is a crash: a non-zero exit is already its own end (`rc=<n>`), and a model that asked and then fell over is not waiting for an answer.
  • no `RESULT.md` was found where the gather looks for one. A card that published owns its report, and a question in its last turn is prose in a finished card.
  • `./repo` holds no commit past its base. A model that asked rhetorically and then went on and committed the work did the work; the question was narration, not a stop.

func AskedEnd

func AskedEnd(jobDir string, rc int) (string, bool)

AskedEnd asks Asked's question of a finished job on disk: the capture's own tail, the gather's own result lookup, and ./repo's own commit count. The rc is the caller's, because the run holds it and the job directory does not.

func AskedReport

func AskedReport(path string) bool

AskedReport reports whether the `RESULT.md` at path is the one this file wrote for a card that ended by asking, rather than a report a card published.

IT ASKS FOR BOTH MARKS -- the `RESULT: ASKED ` opening and the `written-by:` line -- so the answer rests on the whole shape this file writes and not on an opening a card could type. It exists so that writing this report does not silently promote a card's verdict: the absence of a result is what `no-result` is, and a report the machinery wrote for a card that asked is still an absence of the card's own.

func Backoff

func Backoff(retry int, base time.Duration) time.Duration

Backoff is the wait before retry n, where 1 is the first retry: the base doubled n-1 times and capped at five minutes. A base of zero or less takes the 30-second default, so a caller that names no backoff still gets the spec's own.

func Batch

func Batch(in BatchInput) int

Batch runs one batch through scatter, wait and gather and returns the process exit code: 0 only when every card was done and none held, 1 otherwise, 2 when the admission could not even be read.

func BudgetWord

func BudgetWord(unmetered bool, tokens, spent int, observed, partial bool) string

BudgetWord is rule 13's ceiling and what was observed under it, in the four spellings the document names, and it is the ONE place either route renders them:

unmetered   the caller said this provider has no live accounting
-/<n>       nothing was observed, so the run is never reported as under budget
<s>+/<n>    a PARTIAL observation: some columns were dashes, so it can show that the
            budget was reached and can never show that the job stayed under it
<s>/<n>     a whole observation

THE POOL'S `RUN DONE`/`RUN KILLED` AND `native`'s `NATIVE OK` BOTH COME THROUGH HERE (rule 13d: "Everything rule 13 says about the word, the sum, the stop, the record and a source that cannot be read holds for a native card"). Two renderings of one field is how one of them drifts, and a reader who learned the field on one line would misread it on the other.

func BuildContainerArgs

func BuildContainerArgs(engine, image, workDir, model, label, cardContent, secretKey string) []string

BuildContainerArgs constructs the exact podman/docker run command per infra/image/README.md.

func CacheEnv

func CacheEnv(root string, sourceRoot ...string) []string

CacheEnv is the cache environment the harness child carries: one cache root for every job under one swarm root, so N workers do not each download the same toolchain and modules. sourceRoot optionally names the job checkout ASDF maps into its private Lisp overlay.

func CacheRoot

func CacheRoot(root string) string

CacheRoot is the shared cache directory under a swarm root.

func CapacityLine

func CapacityLine(cores int, load1, freeGB, memFreeGB float64) int

CapacityLine is a bench's capacity line: min(cores*1.5 - load1, (free_gb-25)/2, memfree_gb/2), floored to a whole number of workers and never negative (deprecated/docs/SPEC-JOBS.md section 2). A non-positive cores count is a bench with no pinning and no bound on the load term, so the line is the smaller memory term.

func CardCacheRead

func CardCacheRead(u ProviderUsage) (int, bool)

CardCacheRead is the observed cache_read as a number, and whether it was one.

func CardCloneRepos

func CardCloneRepos(text string) []string

CardCloneRepos returns the distinct owner/repo pairs a card's text names as clone URLs, in order of appearance: every `git clone <url>` operand that names github, plus every bare `github.com/<owner>/<repo>` mention.

func CardHeaderChecks

func CardHeaderChecks() []string

CardHeaderChecks is every token this file draws, in one byte-stable order.

func CardIdleLine

func CardIdleLine(task string, e IdleEnd) string

CardIdleLine is the typed line a card ended for idleness is reported on when NO refusal stopped it -- a provider turn that never answered, a tool call that never returned:

CARD IDLE task=<id> step=<n> idle=<n>s

It is deliberately not a WALL line. `js-under-20-bytes` died in a provider stall and was reported `WALL task=... path=/var/db/xcode_select_link`, and a shift went looking at the wall for it.

func CardKindFromText

func CardKindFromText(text string) string

CardKindFromText reads a card's kind from its typed KIND: header or a :kind pull field. An untyped card is "".

func CardNamesRepo

func CardNamesRepo(card []byte) bool

CardNamesRepo reports whether the card names a repository at all, readable or not: such a card that ends with nothing staged is a staging failure (STAGE FAIL reason=no-repo-staged).

func CardParentPaths

func CardParentPaths(lines []string) []int

CardParentPaths returns the 1-based numbers of the card lines that reach above the job, in line order, at most one per line. It reads the lines it is handed and nothing else.

func CardPrompt

func CardPrompt(card []byte) string

CardPrompt is the message a native run hands its harness for a card: the card text byte for byte and, for a typed card (a KIND: one of typedrec.Kinds), the RESULT-FORMAT paragraph typedrec.ResultFormat renders. Since nova-tools#3689 that is the two-line contract: line 1 verbatim, line 2 DONE, ABSTAIN or BLOCKED, an optional note (and, for a kind that needs them, the fields only the model can know); the card wrapper writes every other field (typedrec.Synthesize). A card of any other kind is its own prompt, unchanged.

func CardReadPaths

func CardReadPaths(card []byte) []string

CardReadPaths is every absolute path a card named on a `READ:` line, in the order the card names them, deduped. Only absolute paths are taken: a relative one is inside the job (which is already internal) or is a word in a sentence, and neither is a fence rule.

func CardRepoURL

func CardRepoURL(value string) string

CardRepoURL is the clone URL a REPO: value names: a URL or a local path as written, an owner/name as the forge's https URL ending .git (the shape base-repo: lines and the URL fallback carry, so FindBenchMirror resolves all three to ~/nova-bench/mirror/<name>.git). "" when the value is none of those.

func CardStageBranch

func CardStageBranch(card []byte) string

CardStageBranch is the branch the staged checkout is on, so the card commits on a named branch rather than a detached HEAD: rowan/<label> from line 1 (RESULT: <label> sha=...), rowan/card when line 1 names no label.

func CardUnit

func CardUnit(label, contract, text string) (decide.Unit, bool)

CardUnit reads one card's typed evidence. ok is false where the card names no kind and its contract line reads as none of them: a card nobody can type is a card nobody routes, and today's model stands.

func CheckKillPoint

func CheckKillPoint(point string)

CheckKillPoint is a no-op in the release build: it never reads the environment and never kills. The kill injection point exists only in the swarmtest build.

func CheckPausePoint

func CheckPausePoint(point string)

CheckPausePoint is a no-op in the release build: it never reads the environment and never stops. The pause injection point exists only in the swarmtest build.

func CheckPublicCard

func CheckPublicCard(w Worker, cardText, root string) (string, bool)

CheckPublicCard applies the gate: a non-public worker admits everything. A public worker admits a card only when every clone URL's owner/repo is listed in <root>/public-repos.txt. It returns the offending repo and true when the card is refused; a missing allowlist refuses every card (the first repo where the card names one, "-" where it names none).

func CheckStagedTree

func CheckStagedTree(jobRoot string) error

CheckStagedTree refuses a staged tree that holds a way out of itself: an absolute symlink, or a symlink resolving outside the job root. The error names the link, because the launcher refuses by path.

func ClampTimeout

func ClampTimeout(d time.Duration) time.Duration

ClampTimeout clamps any raw duration to the bounded interval [MinExecutionTimeout, MaxExecutionTimeout] (5s to 2h). Durations below 5s are clamped to 5s; durations above 2h are clamped to 2h.

func ClampTimeoutWithDefault

func ClampTimeoutWithDefault(d, defaultTimeout time.Duration) time.Duration

ClampTimeoutWithDefault clamps d if positive, or returns defaultTimeout clamped to [5s, 2h].

func ContextualReadGlobs

func ContextualReadGlobs(paths []string, testLine string) []string

ContextualReadGlobs is the dispatcher-approved read set that is not a write: `docs/SPEC-*.md`, same-package siblings of a PATHS glob, `testdata/` under the same directory, and the TEST: package's `*_test.go` files.

func CoresList

func CoresList(cores string) ([]int, error)

CoresList parses the cores column into the sorted cores it pins, or nil for "-". A list that does not parse (an empty item, a non-number, a reversed range) is an error, which is how the table refuses it.

func Cost

func Cost(p *Pool, since string, max int, by string, summaryOnly bool, stdout, stderr io.Writer) int

COST READS <pool>/usage/ AND NOTHING ELSE, which is why it answers after a reclaim.

A fleet can burn a session: one fleet ran about 9M tokens and three parallel workflows hit the limit in 20 minutes (Glenn, 2026-09-10). A pool whose cost is invisible is a pool that is discovered to be expensive by being cut off.

A ZERO IS A MEASUREMENT AND A DASH IS AN ABSENCE. COST OK carries `dashes=` so a total with an absence in it is never read as complete.

func CountCardTurns

func CountCardTurns(jobDir string, u ProviderUsage) int

CountCardTurns counts a job's assistant turns: the harness output log's assistant turns, counted the way usage already counts them (assistant rows/lines), or the usage row count where the log has fewer. A missing log answers the usage row count; both missing is zero turns.

func CountCardTurnsIn

func CountCardTurnsIn(logPath string, u ProviderUsage) int

CountCardTurnsIn is the same count against a NAMED log, because the two routes keep the harness's words in two different files and rule 13d says which is which: "Turns are counted as rule 13b counts them, with `<job>/harness-output.log` as the log, since `native` never writes `harness.log`."

THE NAMES ARE NOT INTERCHANGEABLE. `harness.log` has two owners on the native route already -- a batch pins its runner's stdout to it, which is where the NATIVE OK line lands -- so counting "assistant" in it would count the machinery's own lines and not the model's turns. `harness-output.log` is the capture with one writer (issue #608).

func CountRefusals

func CountRefusals(path string) int

CountRefusals reads a harness log and counts its own refusal lines. It reads the file in one pass and never holds more than a line.

func DeniedPathRoots

func DeniedPathRoots(denied string) []string

DeniedPathRoots is the read set a coordinator would have to open IF the denied path was one the child had to read or execute -- a candidate, never a diagnosis. A path reached through a symlink needs TWO roots, because the kernel checks the grant against the resolved target and a coordinator who names one and not the other loses the card again. A launcher in a `bin` directory belongs to the tree above it; anything else is named by its own directory.

Nothing here is specific to Go, to a bench or to a user: every path is read off the denied path itself.

func EffectiveWidth

func EffectiveWidth(b Bench) int

EffectiveWidth is what a batch fills a bench to: its measured width, or, for a row without one, its core count as today, or -1 for unbounded.

func EnsureCacheDirs

func EnsureCacheDirs(root string) error

EnsureCacheDirs makes the three shared cache directories, so the child's first cache write lands in a directory that exists inside the write set.

func ExitAttestHash added in v0.14.0

func ExitAttestHash(secret string) string

ExitAttestHash is sha256(secret) in hex, the only form of the attestation that is written into the slot file (a file in neither the read set nor the write set).

func ExitAttestOK added in v0.14.0

func ExitAttestOK(secret, hash string) bool

ExitAttestOK is whether a reader holds the secret that produced the slot file's hash. A nonce match without this is a record a worker could have written, and is never reclaim.

func ExitPath

func ExitPath(jobDir string) string

func FenceJobPatterns

func FenceJobPatterns(jobDir string) []string

FenceJobPatterns is every pattern that makes ONE JOB internal to the harness's fence: the job directory and everything under it, in both wildcard spellings, and NOTHING ABOVE IT. A card's own `../scratch` and `../wt-<n>` live under the job directory, which is where the card was told to put them; a sibling job's directory does not, and is not this card's.

func FencePermission

func FencePermission(jobDir string, reads []string) map[string]any

FencePermission is the harness `permission` block one job runs under: everything external is DENIED WITHOUT PROMPTING -- a tool error the model routes around, never the auto-reject that ends the run (issue #918) -- and the job's own paths, plus the read-only paths the card named, are allowed. webfetch is denied as well, for the same reason: a run with no terminal has nobody to answer a prompt, and a prompt here is a dead card. The set is deduped so the file this writes is byte-stable for one job.

func FenceReadPatterns

func FenceReadPatterns(reads []string) []string

FenceReadPatterns is every pattern that admits ONE read-only path a card named on its `READ:` line. The harness asks about the path's PARENT with a `/*` on it -- `read` of /sys/kernel/security/lsm asks about `/sys/kernel/security/*` -- so the parent is named beside the path itself, and both wildcard spellings are given for a path that is a directory. A relative path, and the root itself, are named by nobody and dropped.

func FenceRejection

func FenceRejection(raw []byte) (string, bool)

FenceRejection reports the FIRST path the harness's fence rejected in one capture, and whether it rejected anything at all. The first is the one that matters: the model stops at it, and every later line is a consequence of the same missing rule.

func FinalizeByHand

func FinalizeByHand(p *Pool, id string, now time.Time) (int, string)

FinalizeByHand is `finalize --task <id>`: the verb for a person, for one ended job whose runner died before doing it. It is REFUSED while the job's process group is alive, and it is a no-op with FINALIZE OK when the file already exists.

func FindBenchMirror

func FindBenchMirror(benchHome, baseRepo string) string

FindBenchMirror finds the path to the bench's local mirror for baseRepo. Candidate locations: - baseRepo itself, if it is a directory on disk that exists - $HOME/nova-bench/mirror/<repo>.git - $HOME/nova-bench/mirror/<repo> - /tmp/<repo>-mirror.git - /tmp/<repo>.git

func FindCardResult

func FindCardResult(job string) (string, bool)

FindCardResult is THE ONE PLACE a card's published result is looked for: the job root first (ResultPath, the name the legacy runner's records use), then `repo/` and one directory below it, exactly as far as `liftResult` copies from (issue #594). A path that is not a regular file is not a result: a planted symlink is not followed and a FIFO is not a published report (issue #233). It is exported because `native` asks the same question before the batch ever gathers -- whether the harness published anything at all (issue #591) -- and a second, shallower lookup there would call a card that published under `repo/` silent about a run that worked. One lookup, one answer, both sides.

func FriendNames

func FriendNames(busDir string) ([]string, error)

FriendNames returns every name, alias and group the bus roster knows, the list a bench name is refused against: a friend is never a bench.

func FullestBench

func FullestBench(benches []string) (name string, queued int, ok bool, err error)

FullestBench returns the bench with the most cards in its queue/ among the named bench directories, its queued count, and whether any of them holds a card. Ties go to the lexicographically first name so the choice is deterministic.

func GoBuildCacheDir

func GoBuildCacheDir(root string) string

GoBuildCacheDir is the shared build cache (GOCACHE) under a swarm root.

func GoModCacheDir

func GoModCacheDir(root string) string

GoModCacheDir is the shared module cache (GOMODCACHE) under a swarm root.

func GroupAlive

func GroupAlive(pgid int, started string) bool

GroupAlive reports whether ANY process remains in a group.

func GroupMembers

func GroupMembers(pgid, self int) (int, bool)

GroupMembers counts the live processes in a process group other than self, by reading /proc -- the kernel's own table of pid and process group, never a command line.

func HarnessSHA256

func HarnessSHA256(harness string) string

HarnessSHA256 is the lowercase hex sha256 of the harness binary, resolved on PATH when the description names a bare command. An unreadable harness answers the empty string, and the measurement is still written.

func HarnessTail

func HarnessTail(jobDir string) string

HarnessTail is the LAST thing the harness said, bounded: the one diagnosis of a failed job, which lived in <job>/harness.log, was printed by no verb, and was then deleted with the job directory by `reclaim`. A line whose key is wrong had no printed route to the word `unauthorized` (the new-user audit, F5, 2026-09-11).

func HashBytes

func HashBytes(data []byte) string

HashBytes is a revision's identity: the SHA-256 of its bytes. There is no mtime anywhere in this tool -- two revisions with one mtime are two hashes, and an mtime a filesystem rounds is not an identity (rule 16).

func Headroom

func Headroom(cores int, load float64) int

Headroom is how many cards one tick may launch: cores x 1.5 minus the one-minute load, never more than cores in one tick, floored at 0. A non-positive cores count is no pinning and no cap: -1, unbounded.

func HoldsRepo

func HoldsRepo(slot, repo string) bool

HoldsRepo reports whether slot already holds repo cloned in a kept worktree.

func InputLimitEnd added in v0.14.0

func InputLimitEnd(jobDir, end string, rc int, recorded string, extra []string) (string, string)

InputLimitEnd is the ONE place a job's end becomes `input-limit`, so that the dispatcher's own `finish`, the start-up pass that recovers a dead dispatcher's job (rule 17) and the supervisor all name it the same way. It returns the end and the provider's words.

WHICH ENDS IT MAY REWRITE is the D5 question asked of the input limit (finish.go, rateLimitedOutcome): a limit the harness met, backed off from and then answered past is HISTORY -- the completion evidence the supervisor wrote is the outcome -- and a reap's own end (the deadline, the budget ceiling, an unreadable usage source) is the supervisor's verdict from inside the job and is never written over. AND THE SENTENCE HAS TWO SOURCES, IN THIS ORDER: the harness log, and the supervisor's own `exit.json` record (`recorded`) when the log gives nothing. The supervisor classified this job from inside it and wrote the words down; a log that cannot be read at finish -- a Windows replace window, a directory already reclaimed, a disk that moved -- left the class standing with `sc.Limit` empty and `TRIAGE INPUT-LIMIT … : -`, which is the one line this class exists to print (Fable's read of #150, finding 2).

func InputLimitSignalLine

func InputLimitSignalLine(sig InputLimitSignal) string

InputLimitSignalLine is the structured line the supervisor emits from a signal it read: the one line the next reader trusts, `class`, the measured `value` and the `limit` it hit.

func InputLimited added in v0.14.0

func InputLimited(log []byte, extra []string) (string, bool)

InputLimited reports the provider's own words when the harness log says the request did not fit, and whether it said so at all. A phrase counts only on a line whose own label is a provider error mark, or on the line directly under one (ProviderErrorMarks, MarkWords): the table's sentences appear in transcripts that merely QUOTE them, this repository's own docs and a worker's own RESULT.md included.

The quote is the LINE the phrase was found on, with the terminal paint stripped: OpenCode wrote `\x1b[91m\x1b[1mError: \x1b[0mRate limit reached: ...`, and a line that carries the escapes through reads as `\x1b[91m` on the one line a person is meant to read. The quote is bounded to a tail (oneline.TailBytes), because a harness log is 31KB and this is a field on a one-line event.

func IsCardContractLine

func IsCardContractLine(line string) bool

IsCardContractLine says whether line 1 of a card is a contract line in either form. The prefix is anchored at column 0: an indented or quoted RESULT is prose.

func IsCardTemplate

func IsCardTemplate(name string) bool

IsCardTemplate reports whether name is one of the task templates a card is built from (read-pr, probe-row, fix-card). The other names Template answers to -- result, worker, setup, capacity, and the pulse card templates of SPEC-PULSE rule 4 -- are not cards: result is the report's shape, worker is a JSON worker description, setup and capacity are forms, and the pulse names are the cards nova-pulse `cut` renders, all printed verbatim for their own purpose.

func IsNoCard

func IsNoCard(err error) bool

IsNoCard reports whether err is ErrNoCard.

func IsPowerOfTwo

func IsPowerOfTwo(n int) bool

IsPowerOfTwo reports whether n is 1, 2, 4, 8, ...: the only widths a bench is ever measured at.

func IsPublicRefusal

func IsPublicRefusal(why string) bool

IsPublicRefusal reports whether an admission reason came from this gate.

func IsPulseTemplate

func IsPulseTemplate(name string) bool

IsPulseTemplate reports whether name is one of the pulse card templates or the cost table nova-pulse `cut` reads (SPEC-PULSE rule 4). They are cards cut renders, never task templates to wrap.

func JobASDFOutputTranslations

func JobASDFOutputTranslations(sourceRoot string) string

JobASDFOutputTranslations sends a card's writes to its own overlay. The overlay is seeded from the exact-tip prewarm cache before launch; cards never write that seed or another card's compiled output.

func JobLispCacheDir

func JobLispCacheDir(sourceRoot string) string

JobLispCacheDir is the private writable overlay for one card. It lives beside the card's checkout, inside the already-owned job directory, and is reaped with that job.

func JobName

func JobName(worker, card string) string

JobName is a DNS-1123 label for the card's Job: lower case, [a-z0-9-], at most 63 characters, starting and ending with an alphanumeric.

func KeyFileMode

func KeyFileMode(path string) (os.FileMode, bool)

KeyFileMode reports whether the key file is readable by anybody but its owner. It is a NOTE rather than a refusal: a mode this tool refused over would be this tool deciding how somebody else's machine is administered, and a mode nobody mentions is how a key ends up world-readable.

func KillGroup

func KillGroup(pgid int, started string)

KillGroup ends every process in a group.

func KnownStamp

func KnownStamp(started string) bool

KnownStamp reports whether a start timestamp is valid and populated (not empty and not Dash).

func LaunchArgv

func LaunchArgv(provider, goos string) ([]string, error)

LaunchArgv returns the argv for launching a provider on the given OS. The argv is the harness binary followed by the expanded harness arguments: {model} is replaced with the provider's model from the table, {title} with the provider name, and {prompt} with the literal "PROMPT.md". Unknown providers return an error rather than a guessed argv -- a launcher that guesses is a bespoke launcher with extra steps.

func LaunchArgvFor

func LaunchArgvFor(provider, goos string, req LaunchRequest) ([]string, error)

LaunchArgvFor is LaunchArgv for one run: the table's row for provider gives the argv shape, and the request's non-empty fields fill it. This is the one launcher: `nova-swarm native` builds every harness argv here (deprecated/cmd/nova-swarm nativeLaunchArgv), so the table, not a per-provider script and not a literal in the caller, decides the shape a provider is launched with. The table declares OS-specific columns (linuxHarness, darwinHarness) and only those differ between the two OSes.

func LaunchGrace

func LaunchGrace(w Worker) time.Duration

LaunchGrace is the worker description's own, or the default where it names none.

func LaunchRecordHash

func LaunchRecordHash(r LaunchRecord) (string, error)

func LaunchRecordPath

func LaunchRecordPath(evidenceRoot, jobID, nonce string) string

func LaunchRow

func LaunchRow(provider string) string

LaunchRow is the table row a provider launches with: its own row when the table names it, and DefaultLaunchRow otherwise. A table that cannot be read answers the provider itself, so the caller's LaunchArgvFor reports the read error.

func LegGateLine

func LegGateLine(r LegGateResult) string

LegGateLine is the gate's one line: the harvest, the bench it ran on, the sha it ran at, the verdict, and the counts. It is the line the PR body carries under HarvestGateHeading and the line a reader may trust; a card's own gate claims never appear on it.

func LispCacheDir

func LispCacheDir(root string) string

LispCacheDir is the shared ASDF output directory under a swarm root.

func LoadHeadroom

func LoadHeadroom(cores int, load1 float64) float64

LoadHeadroom is the one term of the capacity line the puller enforces itself: cores*1.5 - load1. At or below zero the puller declines to submit.

func LookSandbox added in v0.12.0

func LookSandbox(path string) (string, error)

LookSandbox resolves the wall's binary. A path the caller typed is used as typed; an empty one is the tool's own name on PATH. A miss is a REFUSAL naming the remedy, never a silent run without a wall: rule 11's --no-sandbox is the one workaround and it is typed by a person, never inferred by this tool.

func LostResponse

func LostResponse(tail []byte) bool

LostResponse reports that this attempt's output is a provider read that ended after the request may have been accepted. The caller must not launch the card again on that signal.

func MakeSlotLease

func MakeSlotLease(store, id, owner string, pid int, label string, until time.Time) error

MakeSlotLease writes one lease directory by publish (atomic) for tests and for fixtures: the pid and until are the caller's, not the taker's.

func MeasureWidthFromRounds

func MeasureWidthFromRounds(rounds []SizeRound) int

MeasureWidthFromRounds is the doubling loop over already-run rounds: the width is the last W that held, and the doubling ends at the first round that breaks a rule. No round held is width 0.

func MergeFencePermission

func MergeFencePermission(raw []byte, jobDir string, reads []string) ([]byte, bool)

MergeFencePermission returns the config bytes the job's harness reads: the provider config the caller carried (raw, which may be empty when `--config` named none) with this job's permission block merged into it. A config this side cannot parse is returned unchanged and ok=false -- the copy is not this function's to refuse (a provider config is carried verbatim, #465) -- and the caller says on stderr that the fence went unconfigured.

A permission block already in the carried config is KEPT and added to: a person's own `read` or `bash` rules are theirs, and only the external_directory patterns this job needs are written over.

func MirrorCloneArgs

func MirrorCloneArgs(mirror, target string, noCheckout bool) []string

MirrorCloneArgs is the git argv of the one staging convention, for cards (StageCard) and for ci run (internal/nsprint/ci): a local clone of the bench mirror that borrows its objects and dissociates, so staging never reads the network and a later gc of the mirror cannot take objects from under the clone. noCheckout leaves the worktree empty for a caller that checks out an exact sha next.

func MirrorDue

func MirrorDue(lastSteal, now time.Time) bool

MirrorDue reports whether an idle worker's steal is due: the bench mirror fetches on a five-minute timer, so a worker steals at most once per MirrorTimer. A zero lastSteal is a worker that has never stolen and is due.

func NPMCacheDir

func NPMCacheDir(root string) string

NPMCacheDir is the shared npm cache (NPM_CONFIG_CACHE) under a swarm root.

func NativeBudgetSourceRefusal

func NativeBudgetSourceRefusal(source string, tokens int, unmetered bool, worker *Worker) string

NativeBudgetSourceRefusal is the reason a native launch is refused for a budget nothing can observe, or "" when the launch may proceed. The caller prints it as a NATIVE REFUSED line and exits 2, BEFORE any directory is made.

`tokens` and `unmetered` are the budget word as read; `cardBudget` is whether a worker description set `max_turns` or `max_cache_read`, and `cardBudgetNames` names which, so the refusal tells a caller which field of their description put them here rather than making them guess.

func NativeUsageIntervalRefusal

func NativeUsageIntervalRefusal(interval, deadline time.Duration) string

NativeUsageIntervalRefusal is the reason `native` refuses an interval, or "" when it takes it. The ceiling is the deadline and it is EXCLUSIVE: an interval equal to the deadline would let no sample run at all, because the deadline ends the card at the instant the first sample would have been due.

func NativeUsageSource

func NativeUsageSource(worker *Worker) string

NativeUsageSource is the source a native launch reads its budget from: the worker description's `usage` when `--worker` names one, and `opencode` when there is none. It is one function because "and `opencode` when there is no `--worker`" is a sentence of rule 13d's and not a default anybody should retype.

func NewExitAttest added in v0.14.0

func NewExitAttest() (string, error)

NewExitAttest mints the per-launch attestation secret, drawn from the OS random source in the supervisor's own memory and never written anywhere a worker can read. Only its hash (ExitAttestHash) reaches the slot file before the launch, and the secret itself reaches <job>/exit.json only inside endWith, after the job's process group is dead.

func NewID

func NewID(now time.Time, label string) string

NewID is the task id: a UTC stamp, the label, and a random half, so that two adds in one second cannot collide -- nova-bus's id lesson, which cost a lost note.

func NextRoute

func NextRoute(routes []string, failed string) string

NextRoute is the route after failed in list order, wrapping at the end, never failed itself; a failed route not on the list hands back to the list's first other route. "" when the list has nowhere else to go.

func Nonce

func Nonce() (string, error)

Nonce is twelve hex characters from the OS random source, drawn once per launch and never reused. It is what makes a stale supervisor's identify fail against a reservation that has moved on.

func NormalizePath

func NormalizePath(path string) string

NormalizePath is rule 15's normalization, in one place.

func NotePath

func NotePath(jobDir string) string

func OpenCodeStoreLocations

func OpenCodeStoreLocations(dataHome string) []string

OpenCodeStoreLocations are the paths OpenCode may keep its database at inside ONE job's data home, in the order the reader tries them: the XDG_DATA_HOME spelling this tool exports, then the HOME/.local/share spelling OpenCode derives from HOME on Linux -- the bench carries its auth.json there. Both are joined to the job's own data home, so both are this job's database and never another job's.

func OverMaxInput added in v0.14.0

func OverMaxInput(sc Sidecar, prompt int) (string, bool)

OverMaxInput is the task budget checked BEFORE the launch: `max_input` is the ceiling a task names on the prompt this tool hands the harness, and the refusal carries the MEASURED size beside it.

IT IS A COUNT OF BYTES, not of tokens, and it is named for what it is: this tool has no tokenizer, and a token count it guessed would be a guess about somebody else's provider (SPEC.md, no guessing). It bounds the one thing the dispatcher can measure -- the PROMPT, which carries the task text -- and not the files the worker then opens; those are `--files`, and the class above is what happens when they do not fit anyway.

func OwedMatch

func OwedMatch(f Finding, owed []string) bool

OwedMatch reports whether a finding matches an item on a pull request's owed list. The match is the owed item's own text, compared without case and without punctuation, because a worker retyping an owed line is not expected to retype its commas.

func OwnedCards

func OwnedCards(benchDir, worker string) ([]string, error)

OwnedCards lists the cards a worker already holds on a bench: the taken/<worker>-<name>.card ownership records. A worker drains its own taken/ before it reaches for another bench.

func ParseCardBase

func ParseCardBase(card []byte) (baseRepo, baseSha string, ok bool)

ParseCardBase extracts the base repo and base sha from the card text (ReadCardBase). ok is false when the card names no repo that can be staged.

func ParseRouteList

func ParseRouteList(s string) []string

ParseRouteList reads RoutesEnv's value: routes separated by commas or white space, empty entries dropped, first occurrence winning.

func ParseSlotRange

func ParseSlotRange(s string) (lo, hi int, err error)

ParseSlotRange parses --slots: "" is no range at all (allocate from 1, no ceiling), "<n>" is 1-<n>, and "<lo>-<hi>" is itself. lo is where allocation starts and hi is the last slot it may take; hi 0 means no ceiling.

func PickRoute

func PickRoute(rows []RouteRow, kind string, rounded int, private float64, def string) string

PickRoute chooses the worker description path for (kind, rounded complexity): rows of another kind never match, public-class rows are skipped when touches_private >= 0.5, an exact complexity wins, otherwise the nearest lower complexity for that kind, otherwise the default.

func PidPath

func PidPath(jobDir string) string

The job directory's three durable records.

func PlanBenchLaunch

func PlanBenchLaunch(cores int, load float64, width, queued int) int

PlanBenchLaunch fills a bench up to its width and drops down under load: at most width cards, at most headroom cards, at most queued cards. A width of 0 is an unmeasured bench and fills by cores as today; a non-positive cores count caps by width only.

func PointProviderAtProxy

func PointProviderAtProxy(raw []byte, provider, proxyURL string) ([]byte, bool)

PointProviderAtProxy returns the config with that provider's baseURL set to the proxy. The other options, including an apiKey, stay. ok is false when there is no baseURL to replace: the caller must not claim the harness was pointed at the proxy.

func PrepareLispJobCache

func PrepareLispJobCache(root, sourceRoot string) error

PrepareLispJobCache makes a private copy-on-write seed for one card. A missing prewarm seed is valid and produces an empty overlay; the card will compile there without contaminating any sibling.

func ProfileJobs

func ProfileJobs(pattern string, stdout, stderr io.Writer) int

ProfileJobs reads every timeline a job glob names and prints one PROFILE line per job and one PROFILE SUMMARY line with the mean per phase. A glob match may be a job directory (its timeline.tsv is read) or the timeline file itself; a match with no readable timeline is skipped. The command exits 0 even when the glob matches nothing, printing a zero summary, because an empty fleet is a measurement and not a refusal.

func Prompt

func Prompt(in PromptInput) []byte

Prompt assembles the worker's prompt. Every sentence in it is a failure from the record.

func PromptDefectLine

func PromptDefectLine(id string, cacheRead, max, turns int) string

PromptDefectLine is the report line a budget stop writes into the task's report. One line, the one-line output grammar's fields, no payload.

func PromptSize added in v0.14.0

func PromptSize(jobDir string) (int, bool)

PromptSize is the size of the prompt this tool handed the harness, read back from the job directory -- the one input size the dispatcher can measure, and the thing `max_input` is a ceiling on. It reports whether it could measure at all, so the two callers can differ where they must: the pre-launch check reads an unmeasurable prompt as no bytes, which no ceiling refuses -- a check the tool cannot make is never a refusal it invents -- and the line prints the dash that is rule 12's word for an absence, never a zero.

func ProviderBaseURL

func ProviderBaseURL(raw []byte, provider string) string

ProviderBaseURL is the model's options.baseURL, or empty when the config has none. The proxy is only installed when this is an http endpoint.

func ProviderLaunchFailure

func ProviderLaunchFailure(raw []byte) (ref string, ok bool)

ProviderLaunchFailure says whether an output tail is a provider server error, and returns the provider's ref where the tail carries one. It is the ONE classifier: `run`'s supervisor, the dispatcher's finish and the native runner all ask it the same question.

func ProviderProxyEligible

func ProviderProxyEligible(raw string) bool

ProviderProxyEligible reports whether baseURL is an http(s) endpoint the proxy can stand in front of. Anything else is left for the harness as given.

func ProviderRetryAfter

func ProviderRetryAfter(log []byte) (time.Duration, bool)

ProviderRetryAfter reads the interval a provider named in its own output: the token `retry-after`, then a whole number of seconds. A log that names none returns false, and the caller falls back to --backoff.

func PublicRefusalLine

func PublicRefusalLine(repo, workerName string) string

PublicRefusalLine is the one refusal line the gate prints.

func PublicRefusalWhy

func PublicRefusalWhy(repo, workerName string) string

PublicRefusalWhy is the text after "CARD REFUSED ": the fixed reason, the offending repo, the class and the worker name, each through oneline.Field.

func PublishEvidence

func PublishEvidence(evidenceDir, jobID string, task, prompt, profile []byte, preimage string) error

PublishEvidence writes the three evidence files then MANIFEST last, atomically no-replace, never following a symlink. An existing complete set with identical verified bytes is an idempotent replay; differing content under the same job ID refuses. (SPEC-SWARM-PROFILES, lines 344-365)

func PublishLaunchRecord

func PublishLaunchRecord(root, jobID, nonce string, r LaunchRecord) error

func PullBatch

func PullBatch(in PullInput) int

PullBatch takes up to Batch cards of one kind and one repo from Queue, runs each on the shared clone, clips between cards, and returns the process exit code: 0 when the turn ran, 2 when it could not. A card over its :effort stops the batch and returns that card and every card behind it to queue/.

func PullLanes

func PullLanes(in PullLanesInput) int

PullLanes drains the lanes in order and prints one line. Every path comes from a flag; a queue that cannot be read is a refusal with one remedy line.

func PullQueue

func PullQueue(queueDir, takenDir, worker string, admit int) ([]string, error)

PullQueue takes up to admit cards from a bench's queue/ by renaming each into taken/ as <worker>-<name>.card, the ownership record (deprecated/docs/SPEC-JOBS.md sections 2 and 7). The rename is atomic within the directory, so two workers cannot take one card. A full bench (admit 0) reads nothing and leaves every card in queue/.

The names are sorted before the take, so a test and a reader see the same order; the take stops at admit, so the bench never crosses its capacity line.

func QuarantineCard

func QuarantineCard(queueDir, reason, cardID string, cardContent []byte, meta QuarantineRecord) (string, error)

QuarantineCard atomically places a card into the quarantine hierarchy: <queue>/quarantine/<reason>/<card-id>/.

func QuarantineCardDir

func QuarantineCardDir(queueDir, reason, cardID string) string

QuarantineCardDir returns the directory for a specific quarantined card: <queue>/quarantine/<reason>/<card-id>.

func QuarantineDir

func QuarantineDir(queueDir string) string

QuarantineDir returns the quarantine root: <queue>/quarantine.

func QuarantineReasonDir

func QuarantineReasonDir(queueDir, reason string) string

QuarantineReasonDir returns the path for a specific failure reason: <queue>/quarantine/<reason>.

func QueueCards

func QueueCards(benchDir string) ([]string, error)

QueueCards lists the cards waiting in a bench's queue/, without the .card suffix, in sorted order so two workers walk the same list and race on the same first card. A bench with no queue/ directory has no cards, not an error.

func QueueDir

func QueueDir(benchDir string) string

QueueDir is the bench's queue/ directory.

func QueuedCards

func QueuedCards(store string) ([]string, error)

QueuedCards lists the card files in <store>/queue, in name order. A directory or a non-.card file is not a card. A missing queue/ is an empty queue, not an error: a bench with nothing waiting pulls nothing.

func RateLimited

func RateLimited(log []byte) bool

RateLimited reports whether the harness's own output says the provider answered 429.

The status is read from TEXT rather than the exit code, because POSIX truncates 429 to 173: a harness cannot hand the number back through its exit status. The bare number must sit beside a status word (http, status, code, error) or be one of the phrases a provider uses, so a token count of 429 is not read as a rate limit.

func ReadBenchTable

func ReadBenchTable(path string) (map[string]Bench, error)

ReadBenchTable reads a benches file: one header line then one tab-separated row per bench, the seven columns name, host, root, cores, harness, auth, wall. A row that does not parse is refused with its line number, and a name used twice is refused too.

func ReadJSON

func ReadJSON(path string, v any) error

ReadJSON reads one of them back.

func ReadKey

func ReadKey(path, varName string) (string, error)

ReadKey reads the first line of the key file and returns the key.

The strips, in order: a leading "export ", then a leading "<VAR>=" for this worker's own variable name, then every whitespace character. An empty result is a refusal carrying the command that writes the file, because a refusal that says only "empty" has moved the guessing onto the reader.

func ReadKeyOrSecret

func ReadKeyOrSecret(w Worker) (string, error)

ReadKeyOrSecret is the key the worker description names, by either half (issue #881): read from the key file, or read from this process's own environment under the name the description's `secret` carries. run and supervise both call it, so the variable must be present and non-empty in EACH process's own environment -- run's at admission, the supervisor's when it inherits that environment and starts the harness. It returns the value and an error that names the variable and never the value.

func ReadOwed

func ReadOwed(path string) ([]string, error)

ReadOwed reads an owed list: the items a pull request already knows it owes, one per line, `- ` bullets and blank lines alike. It is a FILE because an owed list is a paragraph a person wrote, and putting it in an argument would put it in the process table (the same reason a task is a file).

func ReadRegular

func ReadRegular(path string) ([]byte, error)

ReadRegular is os.ReadFile for a worker-writable path: the whole file when it is a regular file within MaxRegularRecord, and a refusal when it is anything else or oversized. A symlink is not followed and a FIFO is not opened (security#30, issue #233).

func Reap

func Reap(pgid int, started string, grace time.Duration) (survived bool)

Reap ends a process group: terminate, wait, kill -- and then reports whether anything in it SURVIVED, because a slot whose data home may still have a writer in it is not a free slot, and a pool that re-used it would reproduce the lock failure with a corpse.

func ReapAtTaskEnd

func ReapAtTaskEnd(root string, w Worker, now func() time.Time) (int, int64)

ReapAtTaskEnd is the call `run` makes when a task ends: a worker description that sets keep_data=true leaves every slot's data alone, and one that does not frees the finished slots' data/, tmp/ and jobs/*/scratch under the root.

func ReapExpiredLeases

func ReapExpiredLeases(store string, now time.Time) ([]string, error)

ReapExpiredLeases returns the cards of expired leases whose pid is gone to queue/ and removes those leases, in lease id order. A lease past until= whose pid is alive is DRIFT and is left where it is; a lease still inside its term is live and is left too.

func ReapSlots

func ReapSlots(in ReapInput) (int, int64, error)

ReapSlots removes, for every finished slot under the root, the slot's data/, tmp/ and jobs/*/scratch, keeping RESULT.md, usage.tsv and the logs. It returns the number of slots reaped and the bytes freed. A slot is finished when a job under it published a RESULT.md, or its newest harness log is older than Older. A live slot -- no RESULT.md and a log that has moved recently -- is untouched.

func RecordBenchWidth

func RecordBenchWidth(path, name string, width int, measured, version string) (cores, rows int, err error)

RecordBenchWidth writes width, measured and version onto the bench's row in place and writes the measured table beside it at <path>.measured: one header line and one row naming the bench, its width, stamp and version. It returns the bench's core count and the table's row count for the BENCH WIDTH line.

func RecordStartupCost

func RecordStartupCost(root, harnessSHA256 string, cacheRead, turns int) error

RecordStartupCost writes the known-answer measurement at `<root>/startup-cost.tsv`, a header and one row.

func RecordStartupCostIfAbsent

func RecordStartupCostIfAbsent(root, harnessSHA256 string, cacheRead, turns int) error

RecordStartupCostIfAbsent writes the measurement from the first finished task, and only when no measurement is there yet: the first run has nothing to compare against, so it accepts its budget and leaves the next run a fact.

func RefuseFriend

func RefuseFriend(name string, friends []string) string

RefuseFriend returns the admission refusal when name is on the bus's friends list and empty otherwise. It writes nothing to the bus: it only answers.

func ReleaseQuarantined

func ReleaseQuarantined(queueDir, reason, cardID string) (string, error)

ReleaseQuarantined removes a card from quarantine and restores it to <queue>/<card-id>.card so it can be re-run after defects have been resolved.

func ReleaseSlotLeases

func ReleaseSlotLeases(store, owner, label string, all bool) (released, held int, err error)

ReleaseSlotLeases removes owner's leases: all of them with all=true, or only the ones carrying label otherwise. It reports the removed count and the owner's leases still held. It never frees a seat whose holder is still running; see ReleaseSlotLeasesForcing.

func ReleaseSlotLeasesByID

func ReleaseSlotLeasesByID(store string, ids []string, pid int) (released int, err error)

ReleaseSlotLeasesByID removes EXACTLY the leases named, and only while they are still the caller's. It exists because releasing by owner and label is not releasing by identity: an owner is a bench and a label is a card's name, and two runs that share both -- two slots, two benches, a retry -- would each give away the other's seat (nova-tools#1546, Stella's hold on PR #1562). A holder that took leases learns their ids from TakeSlotLeases and hands exactly those back here.

The pid is a fence, not bookkeeping. An id whose lease has since been reaped and remade by somebody else must not be removed by a stale list, so each lease is RE-READ and left alone unless its pid is the one given. An id that is simply gone is not an error: a release is allowed to be late, and the caller's job is to stop holding, not to prove nobody tidied up first.

`slots release --owner ... --label ...` stays as it is, by owner and label, because a PERSON at a prompt wants exactly that: free whatever this owner is holding for that card. A person can see the store; a deferred cleanup cannot.

func ReleaseSlotLeasesForcing

func ReleaseSlotLeasesForcing(store, owner, label string, all, force bool) (released, held, live int, err error)

ReleaseSlotLeasesForcing is the body, with the live-seat fence spelled out.

A LEASE IS NOT A TICKET SOMEBODY ELSE MAY TEAR UP (issue #1902). Deleting a lease does not stop the process holding it: the holder keeps running, keeps spending, and the seat it is sitting in is handed to the next taker. Johnny freed a live `native`'s only seat from outside and watched a second `native` take it -- two cards on a capacity-1 bench, both printing NATIVE OK -- and a card given --no-wall did the same to a bystander from inside its own shell. `--owner` is an unauthenticated string and every owner on a shared bench is the same unix user, so who CALLED release is not a fence either. The only fence that means anything is the holder: a lease whose pid is ALIVE and is not this process is KEPT and counted in live.

Releasing your OWN lease is always allowed: `run`'s dispatcher and `native`'s cleanup give back the seat they are sitting in while their own pid is alive, and that is the ordinary end of a run rather than a steal. force is the person's loud override, the way --no-wall is the loud way to ask for no containment.

func RenewSlotLease

func RenewSlotLease(store, owner, label string, dur time.Duration, now time.Time) (renewed int, until time.Time, err error)

RenewSlotLease extends the until= of the lease owner holds under label by dur, and reports how many leases it renewed and the latest new until. It renews by owner and label because the heartbeat is its own process: the pid in the lease is the pull worker's for liveness, not the renewer's identity. A lease whose pid is gone is left for the next take to reap.

func ResultByID

func ResultByID(p *Pool, id string, stdout, stderr io.Writer) int

ResultByID is `result --id`: the one path by which a malformed report reaches a person. The body does NOT go through internal/oneline, because the body is the thing asked for.

func ResultPath

func ResultPath(jobDir string) string

func RetriableRateLimit added in v0.14.0

func RetriableRateLimit(log []byte, extra []string) bool

RetriableRateLimit is the question `finish` asks before it holds a slot and spends a second attempt: a 429 the dispatcher should wait out, and NOT an input that did not fit. The input limit is the more specific truth about a log that says both, and it is the one that decides, because the retry a 429 earns is the one thing an oversized task must not get.

func RouteKey

func RouteKey(model, authProfile string) string

RouteKey is the provider, model and key a card's requests share a queue with. The key is named by its PROFILE and never by its value: this string is printed on a STATUS line, and a secret that can reach a log is a secret that will.

func RouteQuestions

func RouteQuestions() map[string]decide.Question

RouteQuestions is the four questions route asks about the card text.

func Run

func Run(in RunInput) int

Run is the dispatcher. It returns the exit code.

func RunPullWorker

func RunPullWorker(ctx context.Context, opts PullWorkerOptions) int

RunPullWorker executes the pull worker loop.

func SQLiteOnPath

func SQLiteOnPath() bool

SQLiteOnPath says whether the one program a usage source needs can be resolved. It is asked BEFORE a launch by rule 13d's refusal and AFTER one by the final read, and both ask it the same way.

func SandboxGate added in v0.12.0

func SandboxGate(sandboxPath, poolDir, secret string, notes io.Writer) (reason, text string)

SandboxGate is what `run` does ONCE, before the first worker: it asks the machine what it can enforce and then proves the wall with the real policy. A failure is a REFUSED RUN with no worker started, because a batch that runs unwalled under a green line is exactly the silent sandbox this whole tool exists to prevent.

It returns the reason= token for the RUN REFUSED line and the sentence beside it, or an empty reason when the wall is there. The two reasons are the spec's own (test 23): a machine with no backend is `no_sandbox` and a wall that failed a check is `sandbox_probe`. `notes` is where the one thing this gate can fail at without refusing the run is said: the probe directory it made and could not remove. It is the caller's stderr, never stdout, because a pass prints EXACTLY ONE `RUN NOTE` and that line is the remedy at the end (SPEC-SWARM's output grammar).

func SandboxProbeDir added in v0.12.0

func SandboxProbeDir(poolDir string) string

SandboxProbeDir is the directory the probe of rule 10 writes in, and it is the dispatcher's own: the probe needs a --write whose PARENT is outside every named path, so that the write it expects to be denied has somewhere to be attempted.

It is ABSOLUTE, because `--pool` is used as typed and the README's own invocation types it relative (`--pool ./pool`). Rule 5 of the wall is "paths are resolved, absolute and existing", so a relative pool sent the probe `--write pool/sandbox-probe`, the wall refused it as relative, and `run` refused the whole pass and started NO WORKER for the documented line (DeepSeek's read of #88 at d0c1841, HIGH 1).

func SchedulerAdmits

func SchedulerAdmits(freeGB, memFreeGB int) int

SchedulerAdmits is how many Jobs of this shape the scheduler places on a node with free_gb of disk and memfree_gb of memory once the floor is reserved: floor(allocatable / request) for each resource, the smaller of the two, never negative. It is the two memory terms of AdmissionLine, enforced by Kubernetes instead of counted by a person.

func ScopeGlobs

func ScopeGlobs(paths []string, testLine string) []string

ScopeGlobs is declared writes plus contextual reads: PATHS, docs/SPEC-*.md, same-package siblings, testdata under those directories, and `<package>/*_test.go` from a `TEST:` line.

func SecretFromEnv

func SecretFromEnv(name string) (string, error)

SecretFromEnv reads the variable a description names as its `secret` from this process's own environment. `nova-secrets exec` is what sets it (docs/SPEC-SECRETS.md, the second caller); the value is never written to a file, never printed, and never in a RUN or SUPERVISE line -- the refusal below names the VARIABLE and the remedy, never the value.

func ShareFromMemory

func ShareFromMemory(totalMemBytes, perCardBytes int64) int

ShareFromMemory derives how many cards a bench may run at once from its total usable memory and a measured per-card figure, both in bytes: the total is divided by the per-card figure scaled by ShareMemoryHeadroom. It is the memory half of a bench's width -- the number that used to be a guess. A non-positive total or per-card figure derives no share: nothing was measured, or there is nothing to divide into, and 0 is "unmeasured", never a number somebody invented.

func ShellDenialReason

func ShellDenialReason(label, jobDir, wall string, rc int, d ShellDenial) string

ShellDenialReason is the ONE line an unread denial owes its caller, and the one place its words live. It separates what was measured from what was not, by construction:

  • MEASURED: the step, the child's exit code, the wall this ran under, the path named, and the shell's own line, quoted.
  • NOT MEASURED: which operation was denied. The field says `operation=unverified` and the sentence says why, so nobody reads a cause into it.
  • THE REMEDY IS A MEASUREMENT, not a guess: re-run the card's own gate against this commit and read its stderr. On a walled run the read set is offered beside it as one candidate among the others.

`wall` is the wall's own name, or empty for a run that had none; a run with no wall is told so and nothing is attributed to a sandbox that was not there.

func Short

func Short(hash string) string

Short is the twelve characters of a hash that an event line carries.

func SizeRoundHolds

func SizeRoundHolds(r SizeRound) bool

SizeRoundHolds answers the three rules for one round: load at most 1.25 x cores, throughput at least 1.5 x the previous round (vacuous for the first round, which has no W/2), and no abstain.

func SlotAdmissionWeight

func SlotAdmissionWeight(kind string) int

SlotAdmissionWeight is how many share units a card of this kind consumes at take time. Unknown and empty kinds weigh 1, so an untyped launch stays one seat.

func SlotHoldings

func SlotHoldings(store, owner string, now time.Time) (held, share int, err error)

SlotHoldings reports how many leases owner holds and the share it holds them within. It is the read `status` prints; it never reaps or grants.

func SlotUtilisation

func SlotUtilisation(store string, now time.Time) (capacity, reserve, held, free int, heldBy map[string]int, shares map[string]int, err error)

SlotUtilisation reports one bench store for `nova-pulse status --slots-store`: capacity and reserve from shares.tsv, held and free after reaping expired leases with a dead pid (expired leases with a live pid are DRIFT and stay held), per-owner held counts and per-owner shares. Free is capacity-reserve-held.

func Slug

func Slug(label string) string

Slug reduces a caller's label to the characters an id may hold. An id is a file name, a directory name and a field on an event line, so it holds no separator, no space and no "=" -- and never becomes nothing.

func StageCloneIdentity

func StageCloneIdentity(repoDir string, id StagingIdentity) error

StageCloneIdentity writes the pool's identity into the job clone's LOCAL git config, so the commit the worker makes carries the pool's name whatever the bench's config held. The write itself runs with the bench config blanked, so a hostile bench config cannot redirect where local lands.

func StageJob

func StageJob(poolDir, jobRoot, repoDir string) error

StageJob is the launcher's staging: the pool's identity, the clone's local git config, and the no-way-out check, in that order. A pool with no identity row is refused before anything is written; a tree with a way out is refused by path. repoDir is the job clone and may be empty where the worker has not cloned yet.

func StageJobTree

func StageJobTree(source, dest string, card []byte) error

StageJobTree clones source into dest as the job clone. When the card declares PATHS:, the working tree is a sparse checkout of those packages and their in-module dependencies only — an unrelated package is not materialized, and the named package's tests still run (#2498 S10). An import that cannot be resolved is not an empty dependency set: staging refuses before the clone, so the worker is not handed a tree with that dependency omitted. A resolved set with no in-module directories is empty and still checks out the PATHS and TEST cones. PATHS: none, or no PATHS: line, is a full checkout so a card that declared no bound keeps the whole tree. dest is typically filepath.Join(jobDir, JobRepo).

func StagingGitEnv

func StagingGitEnv(id StagingIdentity) []string

StagingGitEnv is the environment the launcher exports with every job: the bench's own git config cannot leak into what the worker commits or what the machinery reads, and the pool's identity row is exported as author and committer so a worker cloning after launch commits under the pool's name (SPEC-TOOLWORK §3 rule 1, #1665).

func Stamp

func Stamp(t time.Time) string

Stamp is the one time format this tool writes: a UTC instant, seconds resolution.

func StartJobLease

func StartJobLease(jobDir, label string) (release func(), err error)

StartJobLease takes the lease on jobDir for the running child and returns the release.

It REFUSES rather than returning a release that protects nothing. A *JobLeaseHeldError says another run holds the directory -- the same-path exclusion of issue #1585 -- and any other error says this run could not establish ownership at all, which under rule 3 is the same refusal with a different reason. The caller exits on either, before it has written anything the holder owns.

The release is safe to call more than once. It stops the heartbeat, WAITS for it, and then removes the file only while the file is still this run's -- pid and nonce both.

func StartSlotLease

func StartSlotLease(slotDir, label string) (release func(), err error)

THE SLOT LEASE (issue #1901). The bench store's lease is a COUNT: it says an owner is holding n seats, and it does not say WHICH slot directory a seat is. So two `native` runs with the same --slot and DIFFERENT --label each took a seat, each took its own job lease -- #1585's refusal is about one job directory, and these are two -- and both started against one `<slot>/data`, which is one HOME, one cache and one opencode.db. SPEC-SWARM under **Slots** says a worker has "its own data home" and "a slot is held by exactly one worker"; under **the races, taken out**, two workers on one data home IS the 2026-09-10 `database is locked` failure, closed on purpose.

The slot lease is the job lease pointed at the slot directory, and it is the same file format, the same four rules and the same pid-and-nonce fence -- published whole, an unparseable record held rather than assumed dead, a failure to establish ownership a refusal, and a release that only removes a record that is still this run's. There is no second spelling of ownership on this bench.

It is taken AFTER the job lease so that two runs sharing one job directory keep saying exactly what #1585 made them say. The label it carries is this run's, so the refusal can name the card that is holding the slot.

func StartStamp

func StartStamp(pid int) string

StartStamp is the kernel's own start stamp for a pid: field 22 of /proc/<pid>/stat, the process's start time in clock ticks since boot. It is what tells a live pid from a REUSED one -- a slot file whose pid is alive under a different stamp is a different process wearing an old number, and rule 17 quarantines it rather than adopting it.

func Steal

func Steal(victimDir, worker string, capacity int) ([]string, error)

Steal takes from a victim bench's queue/ everything above its capacity line, renaming each card into the victim's taken/ as taken/<worker>-<name>.card, and returns the names taken. It never empties the victim below its line: each card is counted before it is renamed, and a victim at or below the line is left alone. A negative capacity is an unbounded line and steals nothing.

func StealCount

func StealCount(queued, capacity int) int

StealCount is how many cards an idle worker may steal from a victim whose queue holds queued cards and whose capacity line is capacity: everything above the line, so the victim keeps enough to fill its own workers. A victim at or below its line is left alone; a negative capacity is an unbounded line and nothing is stolen.

func Supervise

func Supervise(in SuperviseInput) int

Supervise is the whole of the supervisor's life. It returns the exit code.

func TakeCard

func TakeCard(benchDir, worker string) (string, bool, error)

TakeCard takes one card from a bench's queue by renaming it into the bench's taken/ as taken/<worker>-<name>.card, and reports the card's name and whether one was taken. The rename is atomic within the directory, so two workers racing for one card cannot both take it: the loser sees the card gone and looks at the next one. An empty queue is ("", false, nil), not an error.

func TakeSlotLeases

func TakeSlotLeases(store, owner string, k int, dur time.Duration, label string, now time.Time, pid int) (ids []string, held, share, free int, holders string, ok bool, err error)

TakeSlotLeases grants k unweighted (weight 1) leases. See takeSlotLeases.

func TakeSlotLeasesKind

func TakeSlotLeasesKind(store, owner string, k int, kind string, dur time.Duration, label string, now time.Time, pid int) (ids []string, held, share, free int, holders string, ok bool, err error)

TakeSlotLeasesKind grants k leases each carrying the kind's admission weight.

func TakenDir

func TakenDir(benchDir string) string

TakenDir is the bench's taken/ directory, the ownership record of its queue.

func TaskkillArgs

func TaskkillArgs(pid int, force bool) []string

TaskkillArgs returns the argument list for Windows taskkill.exe to reap a process tree. - When force is false, it returns ["/PID", "<pid>", "/T"] for graceful tree termination. - When force is true, it returns ["/PID", "<pid>", "/T", "/F"] for forceful tree termination. This function is platform-independent so unit tests on any OS can verify argument construction.

func Template

func Template(name string) (string, error)

Template returns one template by name.

func TemplateNames

func TemplateNames() []string

TemplateNames is every name Template answers to, in a fixed order.

func TerminateGroup

func TerminateGroup(pgid int, started string)

TerminateGroup asks every process in a group to stop.

func ThisOS

func ThisOS() string

ThisOS is the operating system whose list the wall is built from: this process's own, because the wall contains a card on THIS bench.

func TooShortForAPhrase added in v0.14.0

func TooShortForAPhrase(phrase string) (string, bool)

TooShortForAPhrase is what a description's own phrase must clear, and the reason when it does not. `input_limit_phrases: ["limit"]` would class every failed job whose log holds the word `limit` and refuse each one its retry, and non-emptiness was the only floor there was (Fable's read of #150, finding 4). A provider's refusal is a SENTENCE: PhraseFloor characters at least, and a space or a digit in it, so that no single word can be one. The table this repo ships clears its own floor, and a test says so.

func ToolchainRootNames

func ToolchainRootNames(goos string) []string

ToolchainRootNames is the same list as names, in slash form: the provisioning standard's side of the agreement, which is about what a bench HAS rather than about how the wall grants it, and what the class test compares against.

func ToolchainRootOSes

func ToolchainRootOSes() []string

ToolchainRootOSes is every operating system the one list speaks for, sorted, so the class test walks the declaration itself rather than a list of its own that could fall behind it.

func Triage

func Triage(in TriageInput) int

Triage walks the jobs, folds every revision it has not folded, and writes one page.

func TriageQuarantineSummary

func TriageQuarantineSummary(queueDir string) string

TriageQuarantineSummary formats a one-line summary of quarantined cards for triage: TRIAGE QUARANTINE total=<n> input_limit=<n> malformed=<n> provider_exhausted=<n> infra=<n>

func TwoPhaseReap

func TwoPhaseReap(pgid int, started string) (survived bool)

TwoPhaseReap performs a canonical two-phase reap of a process group: 1. Sends SIGTERM (or graceful tree termination) to allow processes to flush/cleanup. 2. Waits up to TerminateGrace (3s) polling for all processes in the group to exit. 3. If any process in the group remains alive, sends SIGKILL (or forceful tree kill). 4. Waits up to TerminateGrace (3s) polling to confirm all processes are reaped. It reports whether any process in the group survived.

func TwoPhaseReapWithGrace

func TwoPhaseReapWithGrace(pgid int, started string, grace time.Duration) (survived bool)

TwoPhaseReapWithGrace performs the two-phase reap using a custom grace duration. If grace <= 0, it defaults to TerminateGrace (3s).

func UnsandboxedLine added in v0.12.0

func UnsandboxedLine(id string, slot int) string

UnsandboxedLine is rule 11's one loud line, per job, printed BEFORE the job starts. It is never a default, is never implied by a missing backend, and no environment variable or file can produce it: it exists only where a person typed --no-sandbox.

func UsageRefusalLine

func UsageRefusalLine(id string, err error) string

UsageRefusalLine is the one line a usage read that did not answer leaves on the record, so that a row of dashes is never silent about the reader it needed.

func ValidWorker

func ValidWorker(worker string) error

ValidWorker reports why worker cannot name a taken card, or nil when it is exactly one safe filename component in the expected worker-name format.

func ValidateLaunchRecord

func ValidateLaunchRecord(r LaunchRecord) error

func ValidateUsageDestination

func ValidateUsageDestination(path string) error

ValidateUsageDestination ensures that path is not an existing directory, its parent directory exists and is a directory, and the target is writable.

func VerifyEvidence

func VerifyEvidence(evidenceDir, jobID string) error

VerifyEvidence checks whether an evidence set is complete and valid. An incomplete set is not a committed snapshot.

func Version8

func Version8(v string) string

Version8 is the row's version: the tool's identity in 8 characters.

func WallCommits

func WallCommits(repoDir string) (branch string, commits int, ok bool)

WallCommits reports the branch a clone is on and the commits it holds past its base -- the commits reachable from HEAD and not from the base it cloned, which is exactly the work a card added before the wall stopped it. The base is the first remote-tracking ref named `dev`, `main` or `master`, else the first `refs/remotes/*`; a clone with no remote refs counts every commit it has, because there is no base to subtract. It reports false when the directory is not a clone or git cannot name the branch, so a report line is only decorated with numbers that are real.

func WallDeath

func WallDeath(jobDir, task string) (string, bool)

THE WALL DEATH (issue #918). A harness that DENIES a tool call returns a tool error the model routes around and keeps working -- that is the configured fence (fence.go). But a harness build that still auto-rejects stops the model there, and the run ends with no RESULT.md while the card's commits sit in ./repo: eight of thirty cards died this way on 2026-09-16 and the batch scored them `no-result`, a model that chose to publish nothing, with the work stranded and unpushed.

WallDeath is what such a run is: the harness's own fence named a path it would not touch, the job published nothing, and ./repo is left with commits. The report NAMES the path and the commits so the harvester pushes the work -- `WALL task=<id> path=<p>` and, when there are commits past the repo's base, `commits=<n> branch=<name>`.

A FENCE REJECTION BESIDE A RESULT IS NOT A DEATH. A run that emitted the line and then published is a model that took the tool error and finished, and it is done. The result is asked first, wherever the gather looks for one (FindCardResult), so the two shapes never trade places.

func WallKind

func WallKind(line string) string

WallKind is the ONE WORD a refusal is reported by -- the `<what>` of the typed line -- and it is read out of the refusal's own sentence rather than guessed: `home` for the wall's own home_outside refusal, `write` and `read` for a path the kernel refused in one direction, `exec` for a refused execution, `fence` for the harness's own auto-reject, and `denied` when the sentence names no direction at all.

func WallLine

func WallLine(task string, w WallRefusal, branch string, commits int) string

WallLine is the ONE line a wall death is reported on, to the coordinator and to the harvester. It names the task, the refused path and the last step the card reached, and -- when the clone holds commits past its base -- the count and the branch, so work a dead card had already committed is not lost with it:

WALL task=<id> path=<the refused path> step=<the last STEP number seen in the log> [commits=<n> branch=<name>]

func WallRefusedLine

func WallRefusedLine(task, kind, path, step string) string

WallRefusedLine is the typed line a refusal is announced on THE MOMENT IT IS SEEN, before anything is decided about it and while the card is still running:

WALL REFUSED <what> <path> task=<id> step=<n>

It is a note, never a verdict: a card that goes on to publish is done, and this line having been printed takes nothing away from it.

func WallStep

func WallStep(log []byte) string

WallStep returns the number on the LAST `STEP <n>` line the card printed, or "" when it printed none. A card that died at its second step says so, so the remedy can name where.

func WatchIdle

func WatchIdle(w IdleWatch, stop <-chan struct{}) <-chan IdleEnd

WatchIdle watches one card until it goes idle, until stop is closed, or forever. It sends AT MOST ONE IdleEnd and NEVER CLOSES THE CHANNEL, because a closed channel is always ready and a caller selecting on it would read a zero end -- and kill a working card -- the instant the watch decided the card was fine. An Idle of zero or less watches nothing and returns a NIL channel, which blocks forever in a select: the run then behaves exactly as it did before this file existed.

func WidthNeedsRemeasure

func WidthNeedsRemeasure(row Bench, currentVersion string) bool

WidthNeedsRemeasure is the adopt step's question: re-measure whenever the tool version changed under the row, or the row was never measured. A machine change arrives the same way, through probe: a bench whose cores no longer match is proved again before it is sized again.

func WorktreePath

func WorktreePath(slot, repo string) string

WorktreePath is the kept worktree for a repo under a slot: <slot>/worktrees/<owner>/<name>, the one place warmth lives. A repo that is not owner/name has no worktree and returns "".

func WrapSecretsExec

func WrapSecretsExec(seat string, cmdArgs []string) []string

WrapSecretsExec wraps a command line with `nova-secrets exec --as <seat> -- <cmd...>` if seat is set.

func WrapTemplate

func WrapTemplate(name string, files int, text []byte) ([]byte, error)

WrapTemplate puts a task's own text under its template's conditions, with the file budget written into the condition that names it, so the number in the prompt is the number the machinery will hold the worker to.

func WriteAskedResult

func WriteAskedResult(jobDir, task, question string) (string, bool, error)

WriteAskedResult writes the card's report when it ended by asking and published none. Like the blocked report it REFUSES to overwrite a result that exists -- a card that published owns its report -- and it says who wrote it on its own line, because a reader must never have to guess whether a worker or the machinery wrote what they are reading.

Line 1 is `RESULT: ASKED <the question, one line>`: the word is the verdict a requeue reads and the ledger counts, and the question itself is on it, bounded and escaped, so the one line a coordinator reads already holds the thing the card is waiting for. The report carries NO findings head, so it can be scored `plan-only` and never `ok` or `clean`: a report the machinery wrote can never be counted as work a worker did.

func WriteBlockedResult

func WriteBlockedResult(jobDir, task, kind, path, step, reason string) (string, bool, error)

WriteBlockedResult writes the card's report when the run ended it and it published none. It REFUSES to overwrite a result that exists -- a card that published owns its report -- and it says who wrote it on its own line, because a reader must never have to guess whether a worker or the machinery wrote what they are reading.

When kind is empty (idle end with no refusal), the report uses CardIdleLine. When kind is set (idle end after a refusal), the report uses WallRefusedLine.

func WriteCardUsage

func WriteCardUsage(path string, row UsageRow) error

WriteCardUsage writes one card's usage.tsv, header line and one row, atomically. The fields follow the same tab- and newline-scrubbing law as the pool's usage file, so the row is always one row.

func WriteJSON

func WriteJSON(path string, v any) error

WriteJSON writes one of this package's small records whole, through a temporary file, fsync and a rename, so that a reader sees a whole record or none.

func WriteMeasuredTable

func WriteMeasuredTable(path, name string, rounds []SizeRound, now time.Time) error

WriteMeasuredTable writes one doubling round beside the benches table: one header line and one row per W, so the width on the row can be re-derived without re-running the bench.

func WriteReceipt

func WriteReceipt(path string, o Outcome, c Contract) error

WriteReceipt writes the receipt beside the report: label, card sha-256, the disposition, the line count, and the run and usage records when they exist.

func WriteStageTimeoutResult

func WriteStageTimeoutResult(jobDir, bench string, secs int) (string, error)

WriteStageTimeoutResult writes the RESULT.md for a stage timeout: RESULT: BLOCKED stage-timeout <bench> <secs>

func WriteTimeline

func WriteTimeline(path string, rows []TimelineRow) error

WriteTimeline writes one card's timeline.tsv atomically: the header line and one row per event, in order. A field is scrubbed of tabs and newlines so the row is always one row.

Types

type AbortedRecord

type AbortedRecord struct {
	Nonce     string `json:"nonce"`
	Reason    string `json:"reason"`
	At        string `json:"at"`
	Survivors int    `json:"survivors"`
}

AbortedRecord is <job>/aborted.json: the durable acknowledgement a supervisor that lost its identify writes BEFORE its own death can be observed, so that a launch's ABSENCE can be established rather than assumed.

type AskedRun

type AskedRun struct {
	Capture   string // the tail of <job>/harness-output.log
	RC        int    // the child's exit code
	Published bool   // a RESULT.md was found where the gather looks for one
	Committed bool   // ./repo holds commits past its base
}

AskedRun is what one finished native run offers the question test: the tail of the card's own capture, the child's exit code, whether a report was found where the gather looks for one, and whether ./repo holds commits past its base. Every field is something the run already knows when the child is gone.

type BaseCheck

type BaseCheck struct {
	Repo string    // a git repository holding the card's base-sha
	Legs FleetLegs // the fleet's leg table
	P95  KindP95   // p95 wall seconds of DONE cards, by KIND; `*` answers for any kind
}

BaseCheck is the evidence the five checks read. A nil table or an empty Repo means that evidence was not handed over.

type BatchInput

type BatchInput struct {
	ID       string        // the batch id, printed on the BATCH line
	Deadline time.Duration // the whole batch's own deadline
	Idle     time.Duration // per-card idle timeout: a card's log not growing this long is killed
	Cards    string        // path to the TSV: label \t slot \t model \t card-path
	Root     string        // the root a card's RESULT.md hangs under
	Runner   string        // the command, one process per card; "" runs `nova-swarm native` in this binary
	// Harness and Auth are the native path's own configuration, read only when Runner is
	// empty: with no runner script a local card runs through this binary's own `native`
	// verb (issue #636), the way a bench row's card already does.
	Harness string
	Auth    string
	// Self is the nova-swarm binary a runnerless batch runs `native` from. Empty resolves
	// this process's own executable, and then "nova-swarm" on PATH: a test driving Batch
	// directly is not nova-swarm, and a batch that ran the test binary ran nothing.
	Self string
	// Slots is the slot range this batch allocates from: "<lo>-<hi>", or "<n>" for 1-<n>.
	// Empty keeps the old behaviour -- allocation from 1 with no ceiling. The cards.tsv
	// slot column is optional in either case; a hand slot outside the range is that card's
	// own admission refusal (issue #618).
	Slots string
	// Worker is the worker description whose class gates admission: when its
	// class is public, a card cloning an unlisted repo is refused with
	// `CARD REFUSED reason=private-source ...` and never starts. The zero
	// Worker is paid and gates nothing, so callers without a description keep
	// today's behaviour byte for byte.
	Worker Worker
	// PullWait and PullPoll bound the pull that brings a remote card's files back: how
	// long to wait for RESULT.md to exist on the bench, and how often to ask. Zero takes
	// the spec's own numbers (30 s, one second), and the tests take short ones.
	PullWait time.Duration
	PullPoll time.Duration
	// SlotsStore and SlotOwner are the bench slot store a card's `nova-swarm native` takes
	// its one lease from, and the owner whose share it counts against (nova-tools#1546).
	// They are REQUIRED of any batch that launches native -- which is every batch without
	// a --runner of its own -- and the refusal is NoSlotsStoreRefusal, said once before a
	// card runs rather than once per card.
	//
	// THE PATH IS ON THE MACHINE THAT RUNS THE CARD. For a local card that is this
	// machine; for a bench row it is the bench, reached over ssh, and the path must exist
	// THERE. One batch spanning local and remote benches therefore wants a store path that
	// means the same thing on each, which today means naming the same absolute path on
	// every bench. Said plainly here because it is the seam a reader will meet.
	SlotsStore string
	SlotOwner  string
	// Tokens is the budget word this batch carries for EVERY card (SPEC-SWARM rule 13d,
	// issue #1545): a number, or the literal `unmetered`. It is REQUIRED -- a batch without
	// it is exit 2 before any card starts -- and it is carried as the caller TYPED it,
	// never parsed here, because rule 13d says the batch puts it "verbatim into every
	// `native` argv it builds" and `native` is the one verb that decides what a budget word
	// means.
	//
	// IT IS EACH CARD'S OWN BUDGET, NOT THE BATCH'S. The same word goes to every card, the
	// shape `batch --tasks` already has: it is never divided among the cards and never a
	// total for the batch. Ten cards under `--tokens 100000` may spend a million tokens
	// between them, and that is what the caller said.
	Tokens  string
	Benches string // path to the benches table; empty means no table is read
	Bench   string // comma-separated bench names to allocate the cards across; empty means local only
	Then    string // a follow-on command, run with sh -c only when every card is done; "" means none
	// MaxInflight is the per-route in-flight cap of nova-tools#917: at most this many of a
	// batch's cards run against one provider/model/key at a time, and the rest WAIT --
	// held, not refused, holding no process and no spend, with their deadlines not begun.
	// Zero is no cap, which is today's behaviour byte for byte.
	//
	// Measured 2026-09-17: above roughly 30-40 concurrent requests on one Muse key the tail
	// latency goes to infinity, and every card launched past that point burns its whole
	// deadline for nothing -- and is paid for.
	MaxInflight int
	// StallAfter is the FIRST-TOKEN deadline of the same issue: a card that has been running
	// this long and has produced nothing at all -- no byte of log, no turn in its harness
	// store -- is ended `ABSTAIN reason=stalled`.
	//
	// IT IS NOT THE IDLE TIMEOUT. `--idle` is about a card that WAS working and stopped, and
	// every signal it has needs a first sample to compare against; a card that never spoke
	// once has no such sample and is invisible to it. On 2026-09-17 a fresh known-answer
	// card hung for its entire 150 s deadline having produced no token at all, while
	// deepseek-flash on the same bench in the same second answered in 11 s. Zero leaves a
	// card to its deadline, which is today's behaviour.
	StallAfter time.Duration
	// THE ROUTE SEAM (Glenn 2026-09-19, SPEC-DECIDE "nova-decide route"). When
	// it is set, the model a card is dispatched with is the ladder's answer --
	// which MIND does this unit of work -- and not the string the fill script
	// wrote in the TSV. The TSV's model becomes the FALLBACK, which is exactly
	// today's behaviour (rule 5): it stands where there is no key, where the
	// provider refused, where the confidence is under the floor and where the
	// rung is a mind a card cannot be dispatched to. Every card carries one
	// receipt line saying which happened. nil leaves the TSV's model alone.
	Route *RouteInput
	// RouteSkip is the reason a launcher was explicitly told not to route.
	// When it is non-empty every admitted card gets a "source":"skipped" row in
	// the route log, so a skipped route is a fact in the log and not an absence
	// (SPEC-DECIDE housekeeping H4, #1625). Empty means the launcher routes; a
	// launcher cannot skip without a reason.
	RouteSkip string
	Stdout    io.Writer
	Stderr    io.Writer
	// contains filtered or unexported fields
}

BatchInput is everything the batch scatter/wait/gather needs, held apart from the command-line parsing so a test can drive it with a fake runner script.

type Bench

type Bench struct {
	Name     string // one word; the local machine is the row "local"
	Host     string // an ssh alias from the caller's own ssh config, or "local"
	Root     string // the swarm root on that host, absolute there
	Cores    string // a taskset list ("1-15", "2,4,6") or "-" for no pinning
	Harness  string // the harness binary on that host, absolute there
	Auth     string // the harness auth file on that host, absolute there, mode 0600
	Wall     string // "sandbox" or "none": what bench probe found
	Width    int    // the measured width, a power of two; 0 is unmeasured
	Measured string // the stamp the width was measured at, or ""
	Version  string // the tool's sha8 the width was measured with, or ""
}

Bench is one row of the benches table: a machine that runs slots.

func LoadBenchTable

func LoadBenchTable(path string) ([]Bench, error)

LoadBenchTable reads --benches TSV, one header line and one row per bench, and validates every row: seven columns in order, an absolute root, harness and auth, a cores column that is "-" or parses, a wall that is "sandbox" or "none", and a name used once, with three optional trailing columns width, measured and version (deprecated/docs/SPEC-SWARM.md "Benches"): a row without them is unmeasured and fills by cores. A row that fails names itself in the error.

func (Bench) Pinned

func (b Bench) Pinned() bool

Pinned reports whether the row pins cores (a list) rather than "-". A pinned row runs each slot on its own core and requires taskset on the bench's PATH.

type Card

type Card struct {
	Name string
	Kind string
	Repo string
}

Card is the affinity facts a card carries: its kind ("go", "lisp", "docs", "schema-leg") and the repo ("owner/name") it runs against.

func ParseCard

func ParseCard(name, body string) (Card, error)

ParseCard reads one card's kind and repo. The card's name is its file name without a .card suffix.

func ReadCardDir

func ReadCardDir(dir string) ([]Card, error)

ReadCardDir reads a bench's queue: a directory of card files in source order. Each card names its kind and its repo on `kind:` and `repo:` lines; the card's name is its file name without the .card suffix. A card that names no repo has no affinity and is refused, never placed blind.

type CardBase

type CardBase struct {
	Repo  string // the clone URL (or a local path) to stage; "" when the card names none that can be read
	Sha   string // base-sha: (or the sha of BASE: <ref>@<sha40>); "" when absent
	Ref   string // BASE: <ref> without @; the ref checked out when there is no sha
	Named string // the raw value of the card's base-repo: or REPO: line, even one Repo could not be read from
}

CardBase is what a card's header says to stage into <job>/repo (nova-tools#3711).

func ReadCardBase

func ReadCardBase(card []byte) CardBase

ReadCardBase is THE reader of which repository a card works in, at which sha: staging (StageCard) and the push-time lint (internal/nsprint/card) both call it, so the repo a card is admitted with is the repo it is staged from. It reads the first 40 lines. Precedence: `base-repo: <url>`, then `REPO: <owner>/<name>` (the header every pushed card carries), then the first github clone URL anywhere in the card (CardCloneRepos). The sha is `base-sha:`, else the sha of `BASE: <ref>@<sha40>`; the ref is BASE:'s value before any @.

type CardHandOff

type CardHandOff struct {
	Repo   string
	Result string
	Moved  bool
}

CardHandOff is what HandOffCardOut did: Repo and Result are the paths it wrote under the out directory ("" when it wrote none), Moved says the repo was renamed (false: copied, the cross-device case, or not handed off at all).

func HandOffCardOut

func HandOffCardOut(job, out string) (CardHandOff, error)

HandOffCardOut hands a native job's work to the card wrapper's out directory: the card's RESULT.md (as swarm.FindCardResult finds it) is copied to <out>/RESULT.md, and <job>/repo is moved to <out>/repo, by rename, or by copy when the two sit on different devices. An out directory that already holds either is left alone for that one (the harness's own copy wins). An empty out is no hand-off. An out that is inside the job, or that is not absolute, is refused: the wrapper would read what a sweep deletes.

type CardHeaderFinding

type CardHeaderFinding struct {
	Check   string
	Line    int
	Excerpt string
}

CardHeaderFinding is one typed-header defect: the check's token, the 1-based line it sits on and the line's own text. It is the shape `deprecated/cmd/nova-swarm/lint.go` prints on a LINT DRIFT line, remedy and all.

func LintCardBase

func LintCardBase(raw []byte, bc BaseCheck) []CardHeaderFinding

LintCardBase returns the base-check findings for one card, in the order the tokens are listed above.

func LintCardDepends

func LintCardDepends(raw []byte, lineup Lineup) []CardHeaderFinding

LintCardDepends returns the depends-on findings for one card. lineup may be nil: an id is then not called unknown.

func LintCardHeader

func LintCardHeader(raw []byte, trust TrustState, required bool) []CardHeaderFinding

LintCardHeader returns the typed-header findings for one card's bytes.

trust may be nil: with no trust state there is no paused kind, and the lint says nothing rather than guessing. required makes the three missing-line tokens apply to a card that carries no typed header at all; without it a card with no header line is left to the twelve older rules, which is what every card written before §5 is.

func LintCardTestGate

func LintCardTestGate(raw []byte) []CardHeaderFinding

LintCardTestGate returns the `test-gate` findings for one card's bytes: a non-empty finding when the card's TEST: line names a package none of its gate commands run.

type CardRoute

type CardRoute struct {
	Model      string
	Rung       string
	Source     string
	Confidence float64
	Floor      float64
	// Fallback is true where today's model stands: no key, a provider refusal,
	// a confidence under the floor, or a rung no card can be dispatched to.
	Fallback bool
	// Why names, in one enumerated token, which fallback this was. It is empty
	// where the answer decided the model.
	Why     string
	Receipt string
}

CardRoute is one card's routing answer: the model it is dispatched with, the rung that answered, where the answer came from, the confidence the floor was applied to, and the one receipt line the card carries.

func RouteCard

func RouteCard(ctx context.Context, in RouteInput, u decide.Unit, todaysModel string) CardRoute

RouteCard is the decision one card's model is chosen by. It never fails: a route that cannot be made is today's model with a receipt that says so, because a dispatch path that refuses to dispatch is worse than one that keeps the behaviour it had.

type CardWallTerms

type CardWallTerms struct {
	Paths    []string // the card's PATHS: globs; empty when PATHS: none or absent
	Reads    []string // default contextual-read globs (not exhaustive): specs, siblings, testdata, TEST:
	Scope    []string // Paths union Reads; what AdmitsRead matches
	NetDeny  bool     // true for header MODE: script; a model job stays nopromise
	Webfetch string   // always FenceDeny
}

CardWallTerms is one card's S7 wall: declared write PATHS, contextual reads, the command allowlist, webfetch deny, and --net-deny when the header is MODE: script.

func WallTerms

func WallTerms(card []byte) (CardWallTerms, error)

WallTerms reads one card's S7 wall. An invalid PATHS: line is an error, the same error hygiene.ValidatePaths returns at cut. Duplicate or contradictory MODE fields refuse. MODE is the typed header's field, not a body line.

func (CardWallTerms) AdmitsRead

func (t CardWallTerms) AdmitsRead(repoRel string) bool

AdmitsRead reports whether a repo-relative path matches the declared writes or the default contextual reads (specs, siblings, testdata, TEST:). Those defaults are not exhaustive: the dispatcher may authorize bounded caller/callee, build-input, and reverse-dependent reads without widening PATHS. A denied required read blocks and requests that adjustment; this helper does not guess. Dispatcher reads (slot, toolchain, read_roots) are not this question.

func (CardWallTerms) AdmitsWrite

func (t CardWallTerms) AdmitsWrite(repoRel string) bool

AdmitsWrite reports whether a repo-relative path matches a declared PATHS glob. Contextual reads are not writes.

func (CardWallTerms) ReadRoots

func (t CardWallTerms) ReadRoots(repoRoot string) ([]string, error)

ReadRoots is the --read directories a directory-covering PATHS glob can name today, under repoRoot, skip-if-absent. A file glob names none: granting its parent would admit siblings outside PATHS. A root that cannot be resolved, or that follows a symlink out of repoRoot, is refused before a policy is produced. TODO launcher: Rowan wires this into nativeSandboxArgv for those directory globs; file-level scope stays the matcher above.

type ClipRequest

type ClipRequest struct {
	Worktree string // the kept worktree under <slot>/worktrees/<owner>/<name>
	Branch   string // the card's topic branch to commit
	Base     string // the base ref the worktree is reset to, e.g. dev or origin/dev
	Message  string // the commit message; empty takes "clip <branch>"
	Result   string // the card's result file inside the worktree, e.g. RESULT.md
	Harvest  string // the directory the result is harvested into
}

ClipRequest is one clip: the kept worktree to reset, the card's branch to commit, the base to reset to, and where the card's result is harvested from and to.

type ClipResult

type ClipResult struct {
	Commit    string
	Harvested string
	Base      string
}

ClipResult is what a clip landed: the branch commit, the harvested result, and the base the worktree now sits on.

func Clip

func Clip(req ClipRequest) (ClipResult, error)

Clip is nova-work clip (section 4): commit the card's branch, harvest its result, then reset the kept worktree to base. The clone is reset rather than re-cloned, so warmth is kept and history never leaks to the next card.

type Contract

type Contract struct {
	Label        string
	ContractLine string // line 1 of RESULT.md must equal this exactly
	MaxLines     int    // bound on evidence lines; 0 or negative means DefaultContractLines
	Card         []byte // the card text, hashed into the receipt
	HaveRun      bool   // a run record exists
	WallSeconds  int    // from the run record
	ExitCode     int    // from the run record
	HaveUsage    bool   // a usage file was given
	TokensIn     int
	TokensOut    int
	USD          string
}

Contract is one result-contract check of a job's RESULT.md.

type CredentialsProfile

type CredentialsProfile struct {
	Kind     string
	Store    string
	Seat     string
	AgeKey   string
	Sops     string
	Gate     string
	Launcher string
}

type DecideFunc

type DecideFunc = decideFunc

DecideFunc is decideFunc under the name the pulse loop can pass across the package boundary: nova-pulse run hands in the Jev client (or its own fake) after each harvest (card 8371).

type Decision

type Decision struct {
	Slot      int
	Kind      string // adopt, reclaim, unknown, unlaunched, quarantine
	Reason    string
	File      SlotFile
	Exit      *ExitRecord
	Remaining time.Duration
}

Decision is what a recovering dispatcher does about one slot file, and the words are the ones rule 17 uses.

type Ending

type Ending struct {
	Sidecar  Sidecar
	JobDir   string
	Provider string
	Model    string
	End      string
	RC       int // -1 is no exit code: unknown, or a launch that never happened
	Started  time.Time
	Ended    time.Time
	Usage    ProviderUsage
	Repo     string
}

Ending is everything finalize needs to know about a job that has ended.

type EvidenceManifest

type EvidenceManifest struct {
	Schema       string `json:"schema"`
	JobID        string `json:"job_id"`
	SnapshotHash string `json:"snapshot_hash"`
}

EvidenceManifest is the prelaunch expected-hash reference published last in the evidence directory (SPEC-SWARM-PROFILES, lines 344-365).

type EvidenceSnapshot

type EvidenceSnapshot struct {
	Task       []byte
	Prompt     []byte
	Profile    []byte
	Manifest   EvidenceManifest
	TaskLen    int
	PromptLen  int
	ProfileLen int
}

EvidenceSnapshot holds the recovered evidence payload with bounded lengths.

func RecoverEvidence

func RecoverEvidence(evidenceDir, jobID string) (*EvidenceSnapshot, error)

RecoverEvidence opens bounded regular files at the constant names, verifies manifest job ID against its directory, recomputes task/prompt/config/prefix hashes before using the snapshot. Missing evidence, mismatched hashes, or a job-ID/path/lineage conflict returns an error.

type ExitRecord

type ExitRecord struct {
	RC        int    `json:"rc"`
	Signal    string `json:"signal,omitempty"`
	Ended     string `json:"ended"`
	Survivors int    `json:"survivors"`
	Nonce     string `json:"nonce"`
	Attest    string `json:"attest,omitempty"`
	End       string `json:"end,omitempty"`
	Spent     int    `json:"spent,omitempty"`
	Observed  bool   `json:"observed,omitempty"`
	Partial   bool   `json:"partial,omitempty"`
	Reason    string `json:"reason,omitempty"`
}

ExitRecord is <job>/exit.json: the supervisor's durable completion evidence (rule 18, step 6). An outcome with none is `unknown`, never guessed. `Attest` is the per-launch secret the supervisor minted in its own memory, written here only inside endWith after the job's whole process group is dead; its hash lives in the slot file, and a reader accepts this record as the supervisor's own word only when both the nonce AND the attestation match.

type FailureClassification

type FailureClassification struct {
	Kind          FailureKind `json:"kind"`
	Reason        string      `json:"reason"`        // Specific reason token, e.g. "rate-limit", "provider-5xx", "input-limit", "malformed", "oom", "test-failed"
	Retriable     bool        `json:"retriable"`     // Whether the failure may be retried automatically
	Quarantinable bool        `json:"quarantinable"` // Whether the failure should quarantine the card when retry budget is exhausted or unrecoverable
	Details       string      `json:"details"`       // Summary or excerpt of the failure
}

FailureClassification is the complete classification decision for a failure.

func ClassifyFailure

func ClassifyFailure(err error, output string, rc int, end string) FailureClassification

ClassifyFailure inspects error, output log tail, return code, and end state to assign a failure classification.

type FailureKind

type FailureKind string

FailureKind is the high-level category of failure under the taxonomy.

const (
	// FailureTransientProvider indicates a temporary provider error (429, 5xx, timeout).
	FailureTransientProvider FailureKind = "transient-provider"

	// FailureInfraCrash indicates an infrastructure or environment crash.
	FailureInfraCrash FailureKind = "infra-crash"

	// FailureTestFailure indicates legitimate test failures where code ran but assertions failed.
	FailureTestFailure FailureKind = "test-failure"

	// FailureCardDefect indicates unrecoverable flaws in the card specification itself.
	FailureCardDefect FailureKind = "card-defect"
)

type Finalized

type Finalized struct {
	UsagePath     string
	UsageExisted  bool
	Class         string
	MalformedLine int
	Hash          string
	Report        Report
	Published     bool
}

Finalized is what finalize did, so the caller can print one line about it.

type Finding

type Finding struct {
	Line     int
	Text     string
	Dup      bool
	Rule     string // the rule quoted verbatim, between backticks
	File     string // normalized: relative, no leading ./, backslashes read as /
	FileLine string
	Job      string
}

Finding is one appended finding line.

func (Finding) Key

func (f Finding) Key(repo, rev string) (string, bool)

Key is the part of rule 15's de-duplication key this finding carries; the report supplies repo and rev. Two findings merge only when both reports carry both AND they are equal.

func (Finding) Quoted

func (f Finding) Quoted() bool

Quoted reports whether this finding carries its rule verbatim with a file:line beside it. A finding with no quote is counted `unquoted` and is not counted `accurate` by anybody: 5 of 67 findings in batch 1 were wrong, each one a rule paraphrased from memory.

type FleetLegs

type FleetLegs map[string]bool

FleetLegs is the set of legs some bench of the fleet carries, lower case.

func ReadFleetLegs

func ReadFleetLegs(p string) (FleetLegs, error)

ReadFleetLegs reads the fleet's leg table: one leg per line, or a TSV whose first column is the leg. Blank lines, `#` comments and a header row whose first cell is `leg` are skipped. Legs are compared lower case.

type Gate

type Gate struct {
	Name    string
	Result  string
	Seconds string
}

Gate is one row of the Gates table.

type GateCommand

type GateCommand struct {
	Name   string // vet | test | gofmt | lint
	Line   string // the command as the gate ran it, one line
	Result string // pass | fail | not run
	Why    string // the not-run reason, or the fail sentence; "" on a pass
	Out    string // what the command printed, verbatim, bounded
}

GateCommand is one gate check's record: the command as the gate ran it, the verdict, why a check did not run or failed, and what the command printed, verbatim and bounded.

type GateRun

type GateRun struct {
	Out  string
	Exit int
	Err  error
}

GateRun is one gate command's own answer: everything it printed, its exit code, and whether it could be run at all. Err is the could-not-run case -- a command that was not found, or a bench that refused it -- and it is never a red exit: a red exit is an answer the gate reads and reports.

func ExecGateRunner

func ExecGateRunner(dir string, argv ...string) GateRun

ExecGateRunner is the shipped GateRunner: one bounded child in dir, its combined output verbatim, its exit code, and Err only when it could not be run at all.

type GateRunner

type GateRunner func(dir string, argv ...string) GateRun

GateRunner runs one gate command with dir as its working directory. The shipped one, ExecGateRunner, execs it on this machine through the PATH; a bench form runs it over the gate bench's own shell, the same seam `nova-pulse harvest --bench` reads; a test's fake records the argv and answers, so no test of this package runs go, gofmt or nova-swarm.

type Handback

type Handback struct {
	Class string        // always ProviderClass5xx
	Ref   string        // the provider's reference, "" when the words carried none
	Wall  time.Duration // the wall at which the provider failed the card
	Route string        // the failed route; the re-deal avoids it
	Next  string        // the route after it in list order, "" when there is none
}

Handback is a card handed back to the next route.

func ProviderHandback

func ProviderHandback(e ProviderExit) (Handback, bool)

ProviderHandback classes one harness exit. ok is true when the harness exited non-zero, the card published no report, and the harness's last lines are the provider's server error -- at any wall.

func (Handback) Line

func (h Handback) Line(label string) string

Line is the one line the launcher reads:

NATIVE PROVIDER-5XX label=<l> ref=<ref|-> wall=<s>s route=<failed> next=<route|-> avoid=<failed>

type IdleEnd

type IdleEnd struct {
	Idle    time.Duration
	Step    string
	Kind    string
	Path    string
	Refused bool
}

IdleEnd is one card ended by the watch: how long it had been still, the step it had reached, and -- when the card's own output named one it never moved past -- the refusal and its one word. A card that simply stopped talking carries no refusal, and saying it hit the wall would be the very misattribution this change exists to stop.

type IdleWatch

type IdleWatch struct {
	Log    string // the card's own capture, <job>/harness-output.log
	Job    string // the job directory: a card that published is finishing, not idle
	Pid    int    // the child's process group leader
	Idle   time.Duration
	Reader *WallReader // may be nil: the watch then reports no refusal
	// contains filtered or unexported fields
}

IdleWatch is one card's watch: the file its own output lands in, its job directory, the leader of its process group, the window, and the reader already in its capture chain. The last three fields are the test's seams and are nil in every real run.

type InputLimitClass

type InputLimitClass string

InputLimitClass is a refusal's class, in the words the structured line carries.

const (
	LimitToken InputLimitClass = "token"
	LimitBytes InputLimitClass = "bytes"
	LimitFiles InputLimitClass = "files"
)

The three ways a request is too big.

type InputLimitSignal

type InputLimitSignal struct {
	Class InputLimitClass
	Value int
	Limit int
}

InputLimitSignal is the structured refusal a harness adapter records: the class, the size that did not fit and the limit it hit, as named fields.

func ReadInputLimitSignal

func ReadInputLimitSignal(log []byte) (InputLimitSignal, bool)

ReadInputLimitSignal reads the adapter's structured line from the harness log. It is the field issue #163 asks a reader to trust: the class, value and limit are named, so no prose rule is asked to decide them. It reports the signal and whether one was there at all.

type Item

type Item struct {
	Text     string
	State    string
	Evidence string
}

Item is one row of the Per item table.

type JobLease

type JobLease struct {
	PID     int
	Host    string
	Label   string
	Nonce   string
	Started string
	Beat    time.Time
	Known   bool
	Path    string
	// contains filtered or unexported fields
}

JobLease is what a lease file says. Beat is the file's mtime, which is the heartbeat, and Known says whether the record parsed at all: an incomplete record names no owner and is never read as a dead one.

func HeldJobLease

func HeldJobLease(err error) (JobLease, bool)

HeldJobLease answers the holder a take was refused for, when that is why it was refused. It is here rather than at the caller so a launcher needs no errors.As of its own.

func ReadJobLease

func ReadJobLease(jobDir string) (JobLease, error)

ReadJobLease reads the lease in jobDir. A job directory with no lease answers an error satisfying errors.Is(err, os.ErrNotExist). A path that is THERE but is not a regular file -- a directory, a symlink, a socket -- is an error of its own: this process cannot tell whether that job directory is held, and rule 3 says it must not guess.

func (JobLease) Live

func (l JobLease) Live(now time.Time) bool

Live reports whether this lease still speaks for a running launcher.

THE KERNEL IS ASKED WHEREVER IT CAN BE: a pid on this host that is gone is gone, and the next run of that card takes the directory over rather than waiting out a window for a launcher that crashed. Two records cannot be asked about -- one written on another host, and one that did not parse -- and for those the heartbeat is the only word there is, so they are HELD until they are older than JobLeaseStale. That is the reaper's second clause, and it is here for the same reason the reaper has it: a liveness file whose owner cannot be checked is not thereby a dead owner.

func (JobLease) String

func (l JobLease) String() string

String is the one line a refusal names a holder with.

type JobLeaseHeldError

type JobLeaseHeldError struct {
	Dir    string
	Holder JobLease
}

JobLeaseHeldError is the refusal a take gets when a run already holds the job directory. It carries the holder so the caller's one refusal line can name it.

func (*JobLeaseHeldError) Error

func (e *JobLeaseHeldError) Error() string

type JobProfile

type JobProfile struct {
	Label   string
	Wall    float64
	Turns   int
	Tools   int
	Seconds map[string]float64
}

JobProfile is one job's folded timeline: its span, how many turns and tool calls it reported, and the seconds each phase spent.

type KindP95

type KindP95 map[string]int

KindP95 is the measured p95 wall, in seconds, of each kind's DONE cards.

func ReadKindP95

func ReadKindP95(p string) (KindP95, error)

ReadKindP95 reads `<kind> <seconds>` rows, tab or space separated; `1526s` is allowed. Blank lines and `#` comments are skipped, and so is a first row whose seconds are not a number (a header). Any later such row is an error: a table that silently dropped a kind would turn a measured bound into a MISSING one.

type KubeJob

type KubeJob map[string]any

KubeJob is a batch/v1 Job manifest. It marshals to JSON, which is YAML, so a k3s auto-deploy manifests directory applies it as written.

func CardJob

func CardJob(name, bench, card, taken, image, runner string) KubeJob

CardJob is the Job for one taken card: one container running the runner with PULL_CARD naming the taken card, the bench mounted at its own path, no retries (the pull is the retry), and the requests and limits that are the capacity line.

type LaunchRecord

type LaunchRecord struct {
	Context          LaunchRecordContext     `json:"context"`
	Control          LaunchRecordControl     `json:"control"`
	EvidenceRoot     string                  `json:"evidence_root"`
	JobID            string                  `json:"job_id"`
	ManifestHash     string                  `json:"manifest_hash"`
	Realization      LaunchRecordRealization `json:"realization"`
	ReservationNonce string                  `json:"reservation_nonce"`
	Sandbox          string                  `json:"sandbox"`
	Schema           string                  `json:"schema"`
	Slot             string                  `json:"slot"`
	UsageEveryNS     string                  `json:"usage_every_ns"`
}

LaunchRecord is the canonical launch record for each reservation.

func ReadLaunchRecord

func ReadLaunchRecord(path string, budget time.Time) (LaunchRecord, error)

type LaunchRecordContext

type LaunchRecordContext struct {
	Kind string `json:"kind"`
	Root string `json:"root"`
}

type LaunchRecordControl

type LaunchRecordControl struct {
	ManifestHash  string               `json:"manifest_hash"`
	Root          string               `json:"root"`
	SandboxSource string               `json:"sandbox_source"`
	Launcher      LaunchRecordLauncher `json:"launcher"`
}

type LaunchRecordLauncher

type LaunchRecordLauncher struct {
	Path   string `json:"path"`
	SHA256 string `json:"sha256"`
	Source string `json:"source"`
}

type LaunchRecordRealization

type LaunchRecordRealization struct {
	EnvHash string `json:"env_hash"`
}

type LaunchRecordRefusal

type LaunchRecordRefusal struct {
	Reason string
}

func (LaunchRecordRefusal) Error

func (r LaunchRecordRefusal) Error() string

type LaunchRequest

type LaunchRequest struct {
	Harness string // the resolved harness binary the caller runs
	Model   string // the provider/model the caller routed this run to
	Title   string // the run's label, the harness's --title
	Prompt  string // the prompt argument the harness is handed
}

LaunchRequest is what one run hands the one launcher beyond the table. Every field is optional: an empty one takes the table's value (Harness: the row's column for goos; Model: the row's model; Title: the provider name; Prompt: the literal "PROMPT.md").

type LeaseRefusal

type LeaseRefusal struct {
	Card    string
	Held    int
	Share   int
	Free    int
	Holders string
}

LeaseRefusal is a take that could not grant: the owner's share is spent or the bench is at capacity minus reserve. It carries the holder list `slots take` prints so the puller can refuse with the same names.

func AsLeaseRefusal

func AsLeaseRefusal(err error) (*LeaseRefusal, bool)

AsLeaseRefusal unwraps err into *LeaseRefusal if it is one.

func (*LeaseRefusal) Error

func (e *LeaseRefusal) Error() string

type LegGateInput

type LegGateInput struct {
	Bench string     // the gate bench the branch was staged on
	SHA   string     // the staged branch's head, the sha the gate ran at
	Dir   string     // the staged branch, outside the wall; "" is refused below, never guessed
	Card  string     // the card file for `nova-swarm lint --card`; "" and the lint is not run
	Paths []string   // the card's PATHS globs; the touched packages come from them
	Run   GateRunner // the command seam; nil is ExecGateRunner
}

LegGateInput is everything the gate needs. The harvest fills it from what it holds: the bench it staged the branch on, the branch's head, the staged tree itself, the card's own PATHS and the card file. It holds no RESULT.md and no self-reported gate: those are data and never an input.

type LegGateResult

type LegGateResult struct {
	Bench    string
	SHA      string
	Commands []GateCommand
	OK       bool
}

LegGateResult is the gate's answer: the bench and sha it ran at, the four commands' records in the issue's order (go vet, go test, gofmt -l, nova-swarm lint --card), and the verdict. OK is true only when no command failed: a check that did not run because the leg names no Go package, or no card was handed, is the leg's own shape and not a failure; a check that COULD not run is a failure and never a pass.

func RunLegGate

func RunLegGate(in LegGateInput) LegGateResult

RunLegGate runs the leg's gate itself on the staged branch and composes the verdict. It reads nothing the card wrote about its own gate: the tree is the only witness.

type Lineup

type Lineup map[string]bool

Lineup is the set of card ids a lineup file named. Nil means no lineup was handed over, which is not the same as a lineup that names nothing.

func ReadLineup

func ReadLineup(path string) (Lineup, error)

ReadLineup reads the card ids a lineup names. A blank line and a `#` comment are skipped. A TSV row contributes its id column; a line with no tab is one id.

type Outcome

type Outcome struct {
	OK        bool
	Line      string   // RESULT OK <label> line2=<...>  or  RESULT REFUSED <label> <reason>
	Line2     string   // the disposition line, verbatim
	Evidence  []string // the lines past line 2, bounded to MaxLines
	LineCount int
}

Outcome is what CheckResult decided: the one grammar line, the disposition, the bounded evidence and the report's whole line count.

func CheckResult

func CheckResult(resultPath string, c Contract) (Outcome, error)

CheckResult reads one RESULT.md and checks it against the contract. It returns an error only when the report cannot be read; a read report always yields an Outcome carrying the grammar line, accepted or refused.

type PidRecord

type PidRecord struct {
	Job        string `json:"job"`
	Slot       int    `json:"slot"`
	State      string `json:"state"`
	Pid        int    `json:"pid"`
	Pgid       int    `json:"pgid"`
	JobPgid    int    `json:"job_pgid,omitempty"`
	PidStarted string `json:"pid_started"`
	JobStarted string `json:"job_started,omitempty"`
	RunnerPid  int    `json:"runner_pid"`
	Started    string `json:"started"`
}

PidRecord is <job>/pid: the same identity, beside the job it belongs to. It carries the pids and the start stamps its readers want, and NO launch nonce: the job directory is the first --write of the wall, so a nonce kept here is readable and writable by the worker itself (packet 2 finding 2).

type Pool

type Pool struct{ Dir string }

Pool is a pool directory. The directory itself must exist -- this tool creates the structure inside one a person named, never the one a person forgot to name.

func OpenPool

func OpenPool(dir string) (*Pool, error)

OpenPool opens the pool at dir and makes sure its structure is there.

func (*Pool) Add

func (p *Pool) Add(text []byte, sc Sidecar) error

Add writes a task's text and its sidecar into pending/.

func (*Pool) Claim

func (p *Pool) Claim(id, from, to string) error

Claim moves a task from one state to another, and the move IS the claim: rename is atomic within a directory, so two dispatchers racing for one pending task produce one winner and one ErrNotExist. The task text is read AFTER the rename, from the path this dispatcher renamed to, never from the pending path it no longer owns.

func (*Pool) ClaimNext

func (p *Pool) ClaimNext() (Sidecar, []byte, bool, error)

ClaimNext claims the oldest pending task, or reports that there is none.

func (*Pool) Decide

func (p *Pool) Decide(n int) Decision

func (*Pool) Finalize

func (p *Pool) Finalize(e Ending) (Finalized, error)

Finalize writes the usage file and the report copy, in that order, before anything moves.

func (*Pool) FindAnywhere

func (p *Pool) FindAnywhere(id string) (Sidecar, bool)

FindAnywhere finds a task's sidecar in whichever state it sits.

func (*Pool) Free

func (p *Pool) Free(n int) error

Free releases a slot: a rename made under slots.lock, and then the removal. Every release -- reclaim, unknown, launch-failed, unlaunched, free-on-finalize -- comes through here.

func (*Pool) FreeIf

func (p *Pool) FreeIf(n int, nonce string) error

FreeIf releases a slot only when the file still carries the launch nonce that selected it. Recovery must never free a newer reservation that reused the number.

func (*Pool) Identify

func (p *Pool) Identify(n int, nonce string, id SlotFile) error

Identify is the supervisor's compare-and-swap (rule 18, step 3): under slots.lock, read the slot file, require that it STILL reads `reserved` with the nonce this supervisor was handed, and only then rename the identity into place. On any other content the supervisor aborts before it spawns anything.

func (*Pool) Jobs

func (p *Pool) Jobs() ([]Sidecar, error)

Jobs is every task the pool knows about whose result triage may count, in id order.

RULE 11 (SPEC-SWARM.md:156): a job whose group left a survivor "moves to `failed/` with `violation=background` in the sidecar, and `triage` does not count it". The result is QUARANTINED: a report written beside a process that was still running when it was read is not evidence, and folding it into a coordinator's page is the one way a quarantined result reaches a person as though it were a finished one. `result --id` is how it is read (rule 15), and it is still counted by `status` and by `cost`.

func (*Pool) List

func (p *Pool) List(state string) ([]Sidecar, error)

List returns every task in a state, in id order -- which is time order, because the id begins with its UTC stamp.

func (*Pool) Orphan

func (p *Pool) Orphan(n int, nonce string) (adopted bool, sf SlotFile, err error)

Orphan rewrites a reservation whose runner is dead to `orphaned`, KEEPING THE NONCE -- under slots.lock, after re-reading the file and rechecking that it still says `reserved` with the nonce first read. If it now reads `launched`, the supervisor's identify landed first and the caller follows the adopt path instead, which is what the bool reports: an orphaning never overwrites an identify that just completed.

func (*Pool) Path

func (p *Pool) Path(parts ...string) string

Path joins a path inside the pool.

func (*Pool) ReadRev

func (p *Pool) ReadRev(id string) (attempt int, hash string, err error)

ReadRev reads the attempt and hash beside a retained report.

func (*Pool) ReadSidecar

func (p *Pool) ReadSidecar(state, id string) (Sidecar, error)

ReadSidecar reads one task's sidecar from a named state.

func (*Pool) ReadSlot

func (p *Pool) ReadSlot(n int) (SlotFile, error)

ReadSlot reads one slot file.

func (*Pool) ReadSlotBy added in v0.13.0

func (p *Pool) ReadSlotBy(n int, budget time.Time) (SlotFile, error)

ReadSlotBy reads one slot file under a caller's deadline: a caller that polls this file inside a bound of its own (the launch handshake) lends that bound to the collision wait, so the retries live INSIDE the caller's clock instead of being added to it. The zero time is "no bound of mine", and reads exactly like ReadSlot.

func (*Pool) ReadUsage

func (p *Pool) ReadUsage() ([]UsageRow, error)

ReadUsage reads every usage row in the pool, in job-id order, which is time order.

func (*Pool) Reclaim

func (p *Pool) Reclaim(id, jobDir string) (freed int64, usagePath string, err error)

Reclaim removes a job directory -- the ONE thing this tool deletes -- and it requires BOTH the usage file and the report copy, because the evidence would otherwise be inside the thing about to be removed. A copy that does not hash to its REV is refused the same way: a persistence that cannot be verified is not a persistence.

func (*Pool) ReportBytes

func (p *Pool) ReportBytes(sc Sidecar) ([]byte, string, error)

ReportBytes reads a job's report from the RETAINED copy for a finalized job and from the job directory only for a running one, so the first triage after a reclaim reads the same bytes it would have read before it.

BOTH READS ARE STEADY. Each of these paths is published by a rename -- the retained copy by `writeAtomic`, the live RESULT.md by the harness -- so on Windows a read landing inside that replace window fails for microseconds and says nothing about the job. Read as a fact it made a published report a `no_result` on the page and threw the worker's findings away: the class this branch closed for exit.json and for `finish`'s read of the same file. A record that is GONE still answers at once, so the ErrNotExist fall-through below keeps its meaning.

func (*Pool) ReportsDir

func (p *Pool) ReportsDir(id string) string

ReportsDir is <pool>/reports/<job>/, the retained record of one job.

func (*Pool) RequeueOnce

func (p *Pool) RequeueOnce(id string, now time.Time) (Sidecar, bool)

RequeueOnce queues one more attempt of a finished task -- the same text as a new id, marked `requeued=1` -- through the existing requeue path (freshAttempt, p.Add). It refuses a task already carrying a requeue, so a provider_error is retried once and never twice (card 8371).

func (*Pool) Reserve

func (p *Pool) Reserve(n int, job, jobDir, nonce string, runnerPid int, now time.Time) error

Reserve writes a slot file in the `reserved` state, under slots.lock, and refuses a slot whose file exists in ANY state: the runner never allocates over an existing file, so the worker cap counts reserved slots as held.

func (*Pool) RetainedReport

func (p *Pool) RetainedReport(id string) (raw []byte, kind string, err error)

RetainedReport reads the retained copy of a finalized job's report, and says which of the three shapes the record takes.

func (*Pool) SlotNumbers

func (p *Pool) SlotNumbers() ([]int, []string, error)

SlotNumbers lists the slots that have a file, in number order. An unreadable name is returned too, as a number that will fail to parse into a decision and be quarantined -- a file this tool cannot read is never a free slot.

func (*Pool) Stopped

func (p *Pool) Stopped() bool

Stopped reports whether a person has asked this pool to stop.

func (*Pool) TakeLock

func (p *Pool) TakeLock(name string, wait time.Duration) (func(), error)

TakeLock takes the named lock inside the pool, waiting up to wait for it, and returns the release. The release is safe to call more than once.

func (*Pool) Text

func (p *Pool) Text(state, id string) ([]byte, error)

Text reads one task's text from a named state.

func (*Pool) UpdateSlot

func (p *Pool) UpdateSlot(n int, nonce string, mutate func(SlotFile) SlotFile) error

UpdateSlot is a compare-and-swap on a launched slot, for the one field the supervisor learns after it has identified itself: the job's own process group.

func (*Pool) UsagePath

func (p *Pool) UsagePath(id string) string

UsagePath is <pool>/usage/<job>.tsv, one per job id, outside everything reclaim removes.

func (*Pool) Where

func (p *Pool) Where(id string) (string, bool)

Where finds the state a task's files are in.

func (*Pool) WriteSidecar

func (p *Pool) WriteSidecar(state string, sc Sidecar) error

WriteSidecar writes a task's sidecar in the state it is in, whole, through a temporary file and a rename.

func (*Pool) WriteUsage

func (p *Pool) WriteUsage(id string, row UsageRow) (string, bool, error)

WriteUsage writes a job's usage file, once and never again: a job's second attempt is a new job id with its own file, so cost sums each attempt once and a retry never double-counts. It reports whether the file already existed.

type PrewarmCommand

type PrewarmCommand struct {
	Name string
	Dir  string
	Env  []string
	Argv []string
}

PrewarmCommand is one cache-producing phase. Run is injectable so unit tests prove the complete plan without compiling the repository or reaching a network.

type PrewarmInput

type PrewarmInput struct {
	Root   string
	Source string
	Repo   string
	Tip    string
	Run    func(PrewarmCommand) error
}

PrewarmInput names one repository tip already present in a local checkout. Prewarm never guesses a branch or fetches a remote: fleet adoption fetches the bench mirror first and hands this verb the exact full commit it is warming.

type PrewarmResult

type PrewarmResult struct {
	Checkout string
	Tip      string
	Phases   int
	Reused   bool
}

func Prewarm

func Prewarm(in PrewarmInput) (PrewarmResult, error)

Prewarm clones an exact tip into the reference path sparse staging already consumes, then fills the bench caches for modules, ordinary builds, compiled test binaries and Lisp. A new reference checkout is renamed into view only after all four phases succeed.

type Profile

type Profile struct {
	ID            string
	Worker        WorkerProfile
	Route         RouteProfile
	EnvVar        string
	Model         string
	AllowedModels []string
	Prompt        PromptProfile
	// contains filtered or unexported fields
}

Profile is the per-job profile record of SPEC-SWARM-PROFILES. Its six binding members -- worker, route, env_var, model, allowed_models, prompt -- are exactly the fields the preimage hashes. The loader never returns a profile that did not pass every bound below.

func LoadProfile

func LoadProfile(path string) (*Profile, error)

LoadProfile reads one profile file and returns the single profile it holds.

The file is a strict-JSON single-profile catalog in the spec's shape:

{"version":1,"profiles":{"<id>":{<profile record>}}}

`version` must be the integer token 1 and `profiles` must hold exactly one entry, so a file names one profile and its id. Every field is validated against its bound or enumerated set; any failure is a *ProfileRefusal naming the field, exit 2.

func (*Profile) Preimage

func (p *Profile) Preimage() (string, error)

Preimage returns sha256:<64 lowercase hex> over exactly the profile record's six binding fields, in RFC 8785 canonical field order, using the same number-free canonical encoding the catalog digest pins (SPEC-SWARM-PROFILES, encoding and admission bounds). Member order, whitespace and equivalent JSON escapes in the source file do not change it; changing any binding field does.

type ProfileRefusal

type ProfileRefusal struct {
	Field string
	Note  string
}

ProfileRefusal is the one failure LoadProfile produces. It names the field -- never the value, because a private path or a prompt can sit in a profile field -- and its exit status is ProfileExitCode.

func (*ProfileRefusal) Error

func (r *ProfileRefusal) Error() string

type PromptInput

type PromptInput struct {
	ID        string
	JobDir    string
	Deadline  time.Duration
	Files     int
	Tokens    string
	Board     string
	Template  string
	Task      []byte
	NoteFile  string
	Result    string
	ResultTmp string
}

PromptInput is everything the prompt says that the task text does not.

type PromptProfile

type PromptProfile struct {
	Mode   string
	Prefix string
	Tools  []string
}

type ProviderExit

type ProviderExit struct {
	Tail   []byte        // the harness's capture; only its last lines are read
	Job    string        // the job directory: a published RESULT.md is the card's own end
	RC     int           // the harness exit code; a clean exit is never the provider's
	Wall   time.Duration // how long the run took: carried on the line, never a condition
	Route  string        // the route that failed (provider/model)
	Routes []string      // the ordered route list; the next one is dealt after Route
}

ProviderExit is what one finished harness exit is judged on.

type ProviderProxy

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

ProviderProxy is the localhost client the harness dials instead of the provider. HarnessURL is the baseURL written into the job config.

func ListenProviderProxy

func ListenProviderProxy(cfg ProviderProxyConfig) (*ProviderProxy, error)

ListenProviderProxy listens on 127.0.0.1 and forwards to upstream. A base URL that is not http or https is refused so the caller can leave it unchanged instead of sending the harness somewhere else.

func (*ProviderProxy) Close

func (p *ProviderProxy) Close() error

Close stops the listener and unblocks a body read still in progress. A read cancelled here is not UNKNOWN: the run is already over.

func (*ProviderProxy) HarnessURL

func (p *ProviderProxy) HarnessURL() string

HarnessURL is the baseURL the harness dials.

func (*ProviderProxy) HeaderWait

func (p *ProviderProxy) HeaderWait() time.Duration

HeaderWait is how long the proxy waits for response headers after the request is written.

func (*ProviderProxy) HeaderWall

func (p *ProviderProxy) HeaderWall() time.Duration

HeaderWall is the measured gap from the written request to the expired header wait. Zero means the header wait did not end the request.

func (*ProviderProxy) Lost

func (p *ProviderProxy) Lost() bool

Lost reports that the body gap or the header wait already fired. A closed channel stays lost.

func (*ProviderProxy) Requests

func (p *ProviderProxy) Requests() int64

Requests is how many upstream requests were started. A request refused after a silent body is not counted.

func (*ProviderProxy) ServeHTTP

func (p *ProviderProxy) ServeHTTP(w http.ResponseWriter, r *http.Request)

func (*ProviderProxy) Silence

func (p *ProviderProxy) Silence() time.Duration

Silence is the gap the body timer is armed with.

func (*ProviderProxy) SilenceWall

func (p *ProviderProxy) SilenceWall() time.Duration

SilenceWall is the measured gap from response headers to the body deadline. Zero means the deadline has not fired, or headers were never seen.

func (*ProviderProxy) Stalled

func (p *ProviderProxy) Stalled() <-chan struct{}

Stalled is closed once, when a body gap or an expired header wait ends the request.

type ProviderProxyConfig

type ProviderProxyConfig struct {
	Upstream   string
	Silence    time.Duration
	After      func(time.Duration) <-chan time.Time
	HeaderWait time.Duration
}

ProviderProxyConfig is one proxy. Silence zero means ProviderBodySilence. After nil means a time.Timer inside readWithinSilence. A test passes After so the production gap is an event it can fire. HeaderWait zero means ProviderHeaderTimeout; a test passes a shorter wait so the suite does not wait 45s.

type ProviderUsage

type ProviderUsage struct {
	Values   map[string]string
	Observed bool // the source answered with a row
	// Turns is the usage row count: the message rows folded into Values, the
	// same assistant-turn count the card budget reads where the harness log
	// has fewer. Zero where nothing was observed.
	Turns int
}

ProviderUsage is what the harness's own accounting said, read from the data home the dispatcher exported for THIS job -- one job, one database, which is why slots exist.

func ReadCardUsage

func ReadCardUsage(dataHome string, started, ended time.Time) (ProviderUsage, string, string, string)

ReadCardUsage reads one card's accounting out of its harness store and returns the values, a note, the store path that answered, and the reason the row keeps its dashes. The data home the native run chose is passed in explicitly, and the reader looks in its standard locations in order rather than guessing one path. The window is the run's own timestamps, widened five seconds each side, read against the store's `time_created` column in milliseconds. The reason is one of the four the NATIVE OK line carries -- no-sqlite3, query-failed, no-rows, or no-store -- or the empty string when the store answered.

TWO OF THOSE FOUR ARE ABSENCES AND TWO ARE FAILURES, and a caller must be able to tell them apart. `no-store` (the harness wrote no database) and `no-rows` (it wrote one and has reported nothing into this window yet) are absences: there was nothing to read. `no-sqlite3` and `query-failed` are READERS THAT STOPPED -- a missing program, a locked or corrupt database, a query past its timeout -- and a caller acting on the numbers is acting on numbers nobody could see. These last two were one token until 2026-09-19, and a caller that reported the pair as a fault cried wolf on every card killed before its first answer.

A note names a condition the caller should carry to the person reading it, most importantly sqlite3 missing from PATH, under which the token columns are dashes and the row still writes rather than the run failing on a number nobody can see.

func ReadCardUsageAfter

func ReadCardUsageAfter(dataHome string, started, ended, notBefore time.Time) (ProviderUsage, string, string, string)

ReadCardUsageAfter is the same read with a FLOOR under the window, and the floor is what keeps a retried card's rows DISJOINT (SPEC-SWARM rule 13d, issue #1545).

THE DEFECT IT CLOSES, measured: the window is widened five seconds each side, because the launch's own clock and the store's need not agree to the millisecond. On a retried card the SECOND launch begins within those five seconds of the first one's end -- the launch grace's retry is seconds, and a test pins it shorter still -- so the second launch's window reached back over the first launch's rows and counted them again. Rule 13d's own worked example is exactly this: two launches finally reported at 40 and 70 "print `budget=110/100`, and their rows hold 40 and 70, never 40 and 110". Before this floor the rows held 40 and 110 and added to 150, which is what a downstream `cost` would have charged.

`notBefore` is the EARLIER launch's end. The zero time is no floor at all, which is what a first launch has and what every caller outside the retry loop wants.

func ReadJobUsageLive

func ReadJobUsageLive(dataHome string) (ProviderUsage, error)

ReadJobUsageLive is rule 13d's LIVE sample: the same statement readOpenCodeUsage runs, against the same database at the same two spellings, and different from it in exactly two ways that the rule names.

IT WAITS FOR NO CHECKPOINT. "A sample reads what the database holds through whatever write-ahead log lies beside it, which is the ordinary state of a database a live harness has open, and it waits for no checkpoint; rule 13's five-second wait belongs to the final read, made when the harness is gone." A live sample that waited for a checkpoint would wait on a writer that is still running and has no reason to close its connection.

AND IT IS BOUNDED BY LiveSampleLimit, not by the tool's 20-second query timeout: a sample that hung for twenty seconds would be a sample that cannot run at a five-second interval.

A DATABASE THAT IS NOT THERE IS NOT AN ERROR, exactly as in the final read: it is the harness having reported nothing yet, which is an absence and not a failure. The three cases rule 13d keeps apart -- nothing observed, a partial observation, a read that FAILS -- are the caller's to tell apart, and this function's error is the third of them.

func ReadProviderUsage

func ReadProviderUsage(source, dataHome string) (ProviderUsage, error)

ReadProviderUsage reads the provider's own accounting for one job, from the source the worker description names (rule 13). There are two: `opencode`, the job's own database in the data home this tool exported for it, and `none`, which reports nothing and under which only `--tokens unmetered` tasks may run.

A SOURCE THAT REPORTS NOTHING AND A SOURCE THAT FAILS TO READ ARE NOT THE SAME THING, and rule 13 rests on the difference: the first leaves the budget unable to fire and the deadline to end the job, and the second, three samples running, ends the job RUN BUDGET-UNVERIFIABLE, because a numeric budget the tool has stopped being able to see is a budget the caller believes is enforced and is not.

func (ProviderUsage) Budget

func (u ProviderUsage) Budget() (sum int, seen int, partial bool)

Budget is rule 13's observed spend: the same arithmetic as Sum over BudgetColumns.

func (ProviderUsage) Sum

func (u ProviderUsage) Sum() (sum int, seen int, partial bool)

Sum adds the token columns that are present, and says whether any were missing, so that a partial observation prints `budget=<n>+/<n>` with the plus rather than passing for a whole one.

type Pull

type Pull struct {
	Bench string
	Slot  string
	Card  Card
	Warm  bool
	Path  string
	Fetch string
}

Pull is the card pull takes and where it comes from. Warm is true when the bench already held the repo in a kept worktree, so the clone is reused; otherwise Fetch names the bench mirror the cold clone comes from.

func Prefer

func Prefer(benches []PullBench) (Pull, error)

Prefer is the pull decision of section 4. It prefers the first card, in bench order and then queue order, whose bench already holds the card's repo in a kept worktree; with no warm bench it falls back to the first card and a fetch from that bench's mirror. It refuses an empty queue rather than inventing a card.

type PullBench

type PullBench struct {
	Name   string
	Slot   string
	Mirror string
	Queue  []Card
}

PullBench is one bench pull may take a card from: its slot directory, the queue in pull order, and the bench mirror a cold clone fetches from.

type PullCard

type PullCard struct {
	Label  string
	Kind   string
	Repo   string
	Effort int
	Path   string // the card's path in queue/, so the returned remainder can go back
}

PullCard is one card as the puller reads it from queue/: its label, the kind and repo that decide which cards may share a batch, and its :effort, the turn bound above which the card stops the batch.

type PullInput

type PullInput struct {
	Queue   string // required: the directory cards wait in
	Clone   string // required: the one kept clone every card in the batch runs on
	Harvest string // required: where a card's RESULT.md lands, under <harvest>/<label>/
	Batch   int    // required: the most cards one turn takes; at least 1
	Kind    string // optional: take only cards of this kind
	Repo    string // optional: take only cards of this repo

	// Run runs one card on the kept clone and reports the turns it used. A turn past the
	// card's :effort stops the batch.
	Run func(c PullCard) (turns int, err error)
	// Clip commits the card, harvests its RESULT.md to result, and resets the clone to
	// base, so the next Run starts from a clean tree.
	Clip func(c PullCard, result string) error

	Stdout io.Writer
	Stderr io.Writer
}

PullInput is one pull turn, held apart from the command line so a test can drive it with a fake turn and a fake clip.

type PullLanesInput

type PullLanesInput struct {
	Queue  string
	Score  lanes.Scorer
	Floor  float64
	Stdout io.Writer
	Stderr io.Writer
}

PullLanesInput is everything nova-swarm pull-lanes needs. The name avoids the dev-landed Pull type in warm.go, which is the affinity pull of section 4.

type PullResult

type PullResult struct {
	Owner string
	Card  string
	Lease string
	Until time.Time
}

PullResult names the card a pull owns and the lease it holds it under.

func PullLease

func PullLease(store, owner string, dur time.Duration, now time.Time, pid int) (PullResult, error)

PullLease takes the next card from <store>/queue under a fresh one-slot lease. It reaps first, so a dead worker's lease returns its card to the queue before this pull considers it. With no card it returns ErrNoCard; when the take refuses, no card is moved and it returns *LeaseRefusal. It never reaches for a card without a lease.

type PullSubmitInput

type PullSubmitInput struct {
	Bench  string
	Worker string
	Cores  int
	Load1  float64
	Image  string
	Runner string
	Submit func(name string, job KubeJob) error
}

PullSubmitInput is one puller turn. Submit creates the Job; the verb writes it where k3s applies it, a test records it.

type PullSubmitResult

type PullSubmitResult struct {
	Worker   string
	Declined bool
	Headroom float64
	Card     string
	Lane     string
	Taken    string
	Job      string
}

PullSubmitResult is what one turn did: declined on the load line, took nothing from an empty queue, or took Card from Lane into Taken and created Job.

func PullSubmit

func PullSubmit(in PullSubmitInput) (PullSubmitResult, error)

PullSubmit is one puller turn: check the load line, take one card by rename, create its Job. A Job that cannot be created returns the card to its lane, so a failed submit never strands a taken card with no Job.

type PullWorkerOptions

type PullWorkerOptions struct {
	Bench     string        // bench name (e.g. "hulk", "space")
	Slots     int           // slot count (default 1)
	Seat      string        // bench seat name for nova-secrets exec
	Store     string        // bench store directory holding queue/, taken/, slots/
	Harvest   string        // directory where card RESULT.md is saved
	Image     string        // container image name
	Model     string        // default model
	Runner    string        // custom runner command (overrides container execution)
	Container string        // container engine ("podman", "docker", or "none")
	For       time.Duration // lease duration (default 30m)
	Once      bool          // if true, run at most one card and exit
	Stdout    io.Writer
	Stderr    io.Writer

	// RunCard is an optional test hook to execute a card instead of the container/runner command.
	RunCard func(ctx context.Context, label, cardPath, workDir string) (int, error)
}

PullWorkerOptions specifies the configuration of a pull worker.

type QuarantineRecord

type QuarantineRecord struct {
	CardID        string      `json:"card_id"`
	Reason        string      `json:"reason"`
	FailureKind   FailureKind `json:"failure_kind"`
	Attempts      int         `json:"attempts"`
	QuarantinedAt string      `json:"quarantined_at"`
	Worker        string      `json:"worker,omitempty"`
	OriginalPath  string      `json:"original_path,omitempty"`
	Details       string      `json:"details,omitempty"`
}

QuarantineRecord describes a quarantined card's metadata.

func ListQuarantined

func ListQuarantined(queueDir string) ([]QuarantineRecord, error)

ListQuarantined inspects <queue>/quarantine and returns all quarantined cards sorted by reason and card id.

type ReapInput

type ReapInput struct {
	Root     string
	Older    time.Duration
	DryRun   bool
	KeepData bool
	Now      func() time.Time
}

ReapInput is one reap pass. Root is the swarm root whose slots are reaped; Older is the age a harness log must pass, with no RESULT.md, for the slot to count as finished; DryRun counts without removing; KeepData is the worker description's keep_data, which leaves the slot's data/ where it is. Now is injectable for tests.

type ReconcileRecord

type ReconcileRecord struct {
	Path     string       `json:"path"`
	CardID   string       `json:"card_id"`
	Action   TriageAction `json:"action"` // ActionRetry (recovered to queue) or ActionQuarantine
	Reason   string       `json:"reason"`
	Attempts int          `json:"attempts"`
}

ReconcileRecord records the reconciliation of one card rescued from limbo.

func QuarantineGC

func QuarantineGC(queueDir string, maxAttempts int) ([]ReconcileRecord, error)

QuarantineGC performs a full garbage collection and reconciliation pass on queueDir:

  1. Reconciles all limbo files (.provider-failed) via ReconcileQueueLimbo, transitioning exhausted or defective cards to quarantine/ and restoring retriable cards.
  2. Cleans up orphan lock files (.lock) in queue and taken directories where no process holds a live flock.
  3. Prunes empty reason directories in quarantine/.

func ReconcileQueueLimbo

func ReconcileQueueLimbo(queueDir string, maxAttempts int) ([]ReconcileRecord, error)

ReconcileQueueLimbo sweeps queueDir and its taken/ subdirectories for any cards stuck in limbo with `.provider-failed` (e.g. `<card>.provider-failed`, `<card>.card.provider-failed`, or files in `.provider-failed/` directories).

If attempts < maxAttempts: restores the card to <queueDir>/<card-id>.card so it returns to the active queue. If attempts >= maxAttempts: moves the card to <queueDir>/quarantine/provider-exhausted/<card-id>/. Removes the limbo artifact so no cards are left in limbo.

type Report

type Report struct {
	Class         string
	MalformedLine int
	Heading       string
	HasHead       bool
	Findings      int // the head's own number, the worker's count
	NotesRead     int
	HasNotesRead  bool
	Repo          string
	Rev           string
	Paragraph     string
	Items         []Item
	Gates         []Gate
	LeftOwed      []string
	OneLine       string
	FindingLines  []Finding
	Hash          string
	Bytes         int
}

Report is one parsed RESULT.md.

func ParseReport

func ParseReport(data []byte) Report

ParseReport reads one RESULT.md into a Report. It never returns a finding from a report it classifies malformed.

func (Report) FindingCount

func (r Report) FindingCount() int

FindingCount is the ONE number a report has: the head's own, where there is a head, and the finding lines where there is none. A report killed before it wrote its head still carries what it found (rule 3, and demanded test 8's headless report with one appended line).

func (Report) Green

func (r Report) Green() int

func (Report) NotDone

func (r Report) NotDone() int

func (Report) Red

func (r Report) Red() int

Red, Green and NotDone count the item states.

type ResultLint

type ResultLint struct {
	Disposition string
	Findings    []ResultLintFinding
}

ResultLint is what LintResult found. Disposition is line 2's first word when it is one of the grammar's, "" otherwise.

func LintResult

func LintResult(raw []byte, baseSHA string) ResultLint

LintResult lints one RESULT.md's text. baseSHA is the caller's base pin, used when line 1 carries no sha=; "" means none was handed over.

func (ResultLint) CountsAsDone

func (r ResultLint) CountsAsDone() bool

CountsAsDone is the one question the harvest asks: is this a DONE it may count?

type ResultLintFinding

type ResultLintFinding struct {
	Check   string
	Excerpt string
}

ResultLintFinding is one defect of one RESULT: the check and the line-2 text it saw.

type RouteError

type RouteError struct {
	Reason string
	Err    error
}

RouteError is a route refusal with a reason token: "no-key" when the named environment variable is unset, "provider-error" when the call could not be made or its answer was malformed. The caller prints it as ROUTE REFUSED.

func (*RouteError) Error

func (e *RouteError) Error() string

func (*RouteError) Unwrap

func (e *RouteError) Unwrap() error

type RouteInput

type RouteInput struct {
	Registry *decide.Registry
	Decide   decideFunc
	Floor    float64
	// Log is the escalation log every decision is written to (rule 8), and
	// Usage is the fleet's usage TSV a provider call's spend is written to.
	// Both are required before a call is made; see Accountable.
	Log   string
	Usage string
	Now   func() time.Time
}

RouteInput is one dispatch route's seam: the ladder, the decider, the floor and where the two records go.

Decide is the same decideFunc the triage route uses -- a *decide.Client's Decide method in production, an httptest fake or an in-process function in a test -- so nothing here ever dials a provider on its own. A nil Decide is the key being absent: the rules answer, no call is made, and today's model stands.

func (RouteInput) Accountable

func (in RouteInput) Accountable() (bool, string)

Accountable reports whether this input may ask the provider at all: token spend reporting is an obligation and every decision is logged, so a route that cannot say where both records go does not make the call. The reason is the line the caller prints once.

type RouteProfile

type RouteProfile struct {
	Provider    string
	Endpoint    string
	Credentials CredentialsProfile
}

type RouteResult

type RouteResult struct {
	Kind        string
	KindConf    float64
	Complexity  int
	NeedsStrong float64
	Private     float64
	Worker      string
	Below       []string
	BelowFloor  bool
}

RouteResult is one card's typed decision and the worker it names. BelowFloor is true when the kind's confidence was under the floor: the worker is then the default and the decision is a suggestion, never an authorization.

func RouteText

func RouteText(text, baseURL, keyEnv string, rows []RouteRow, floor float64, def string) (RouteResult, error)

RouteText makes the typed decision over the card text (bounded to its first 1500 characters, the state rule 1 allows) and picks the worker. def is the worker a below-floor answer keeps. It returns a *RouteError for a missing key or a provider failure, and never prints anything: the caller owns the line.

func (RouteResult) Line

func (r RouteResult) Line(cardBase string, floor float64) string

Line renders the ROUTE line for a card: the same line `nova-swarm route` prints, with the worker the decision chose (the default below the floor) and the below list naming every answer under the floor. cardBase is the card file's base name.

type RouteRow

type RouteRow struct {
	Kind       string
	Complexity int
	Worker     string
	Class      string
}

RouteRow is one row of the routes table: the worker description path for (kind, complexity), where class is public (a free/contributor route) or paid. A public-class worker is skipped for private material.

func ParseRoutes

func ParseRoutes(path string) ([]RouteRow, error)

ParseRoutes reads the routes table: kind<TAB>complexity<TAB>worker json path<TAB>class, one per line, class public|paid. Anything else is a refusal, never a guess.

type RunInput

type RunInput struct {
	Pool          *Pool
	Worker        Worker
	Key           string
	Workers       int
	Hours         float64
	Max           int
	LaunchTimeout time.Duration
	UsageInterval time.Duration
	Backoff       time.Duration // the wait before retrying a 429; zero takes the default
	// NoAutoRetry finalizes deadline and true-429 outcomes without creating a
	// descendant. It is a dispatcher-invocation policy; false preserves automatic retries.
	NoAutoRetry    bool
	Stdout, Stderr io.Writer
	Now            func() time.Time
	Supervisor     string // this binary, re-invoked as `supervise`
	WorkerFile     string // the worker description, handed on to the supervisor
	// THE LAUNCH SEAM (docs/SPEC-SANDBOX.md, the dispatcher caller). Sandbox is the
	// resolved nova-sandbox binary every job runs inside. NoSandbox is rule 11's one loud
	// workaround, typed by a person: it runs the jobs with no OS containment and says so
	// once per job. The two are exclusive and the verb refuses both at once.
	Sandbox   string
	NoSandbox bool
	// THE BENCH SLOT LEASE (deprecated/docs/SPEC-SWARM.md, "Bench slot leases"). When SlotsStore
	// names a store the dispatcher holds one lease per running task, labelled with the
	// task id and released the moment the task ends; it never launches past SlotOwner's
	// share. Both empty is the behaviour without a store, unchanged.
	SlotsStore string
	SlotOwner  string
	// SlotPoll is how long a refused take waits before it asks again; zero takes the
	// 10s the spec names. SlotPID is the pid written into each lease; zero takes this
	// process, so a dispatcher that dies leaves leases the next take reaps.
	SlotPoll time.Duration
	SlotPID  int
	// THE ROUTE SEAM (issue #1486, SPEC-DECIDE "nova-decide route"). When it is
	// set, a claimed task is asked of the ladder -- which MIND does this unit of
	// work -- before it is launched, and a card the ladder marks ask-child or
	// ask-bus is parked in routed-out/ rather than run on this pool's mechanical
	// model. The worker's model is the FALLBACK, which is exactly today's
	// behaviour (rule 5). nil leaves every claimed task to the launch, which is
	// the behaviour every existing caller has today.
	Route *RouteInput
	// CloneFrom is the on-disk checkout a job clone is staged from. Empty leaves
	// the card to clone itself (today's STEP 1), unless the card declares PATHS:
	// prepare then sets this from the pool's reference checkout
	// ref/<owner>/<name>@<rev> when that checkout is present (#2498 S10).
	// When set, prepare stages <job>/repo as a sparse checkout of the card's
	// PATHS packages.
	CloneFrom string
}

RunInput is one dispatcher run.

type SandboxJob added in v0.12.0

type SandboxJob struct {
	Sandbox  string // the resolved nova-sandbox binary
	PoolName string // the container name on windows; accepted and ignored elsewhere
	SlotDir  string // the worker home for this job: the slot copy
	JobDir   string // the job directory: the first --write, and the cwd
	DataHome string // the per-job data home, which is also the child's HOME
	// CacheDir is the SHARED per-bench cache root under the swarm root (issue #1048). It is
	// a --write BESIDE the job directory and the data home: the toolchain and modules are
	// the same for every job under one root, so they live once. Empty when the caller has no
	// shared cache root to name.
	CacheDir  string
	ReadRoots []string // toolchain roots the worker description named
	Command   string   // the harness, resolved on PATH by the caller
	Args      []string // the harness's own arguments
}

SandboxJob is one job's wrap, and every field of it is the dispatcher's own.

func (SandboxJob) SandboxArgv added in v0.12.0

func (j SandboxJob) SandboxArgv() []string

SandboxArgv is the wrap: nova-sandbox's flags, then --, then the harness verbatim. Everything after -- is the harness's own argv unchanged, so no argument is re-parsed and no quote re-interpreted (rule 12).

func (SandboxJob) SandboxCommand added in v0.12.0

func (j SandboxJob) SandboxCommand() []string

SandboxCommand is the whole argv, the binary first, which is what exec.Command is handed.

type ShellDenial

type ShellDenial struct {
	Path string
	Step string
	Line string
}

ShellDenial is one denial a card's own shell reported: the path it named, the step the card had reached, and the line itself, which is the only thing a person can act on. There is no field for the operation, because the text does not carry one.

func ShellDenied

func ShellDenied(log []byte) (ShellDenial, bool)

ShellDenied reports the FIRST denial a card's shell printed in one capture, and whether it printed any. The first is the one that matters: every later line is downstream of the same closed path.

type ShellDenialReader

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

ShellDenialReader is ShellDenied asked of the bytes AS THE PARENT RECEIVES THEM (Johnny's hold on #1478 at 29047871, the #1892 class). The run used to ask ShellDenied of `<job>/harness-output.log` by path after the child was gone -- a file inside the card's own --write directory, and the card's cwd. A card could replace that name after printing the denial (unlink and write a clean file), and a read error left the denial empty: either way the run said NATIVE OK. This writer sits in the capture chain beside the logs, so the verdict is taken from the parent's own copy of the child's output, and nothing the card does to a file afterwards reaches it.

It answers exactly what ShellDenied answers of the same bytes: the FIRST denial, with the LAST `STEP <n>` in the whole capture. It is safe for the child's concurrent stdout and stderr copies and never fails a write, so it can never take the card's own log down with it.

func NewShellDenialReader

func NewShellDenialReader() *ShellDenialReader

NewShellDenialReader returns an empty reader for one run's capture.

func (*ShellDenialReader) Denied

func (r *ShellDenialReader) Denied() (ShellDenial, bool)

Denied is the first denial the capture held and whether it held one. A last line the child left without a newline is read too, as ShellDenied reads the end of a file, without being consumed, so a later Write still completes it.

func (*ShellDenialReader) Write

func (r *ShellDenialReader) Write(p []byte) (int, error)

Write consumes the child's output line by line.

type Sidecar

type Sidecar struct {
	ID          string   `json:"id"`
	Label       string   `json:"label,omitempty"`
	Template    string   `json:"template,omitempty"`
	Files       int      `json:"files"`
	Tokens      int      `json:"tokens,omitempty"`
	Unmetered   bool     `json:"unmetered,omitempty"`
	Deadline    string   `json:"deadline"`
	Batch       string   `json:"batch,omitempty"`
	From        string   `json:"from,omitempty"`
	Requeued    int      `json:"requeued,omitempty"`
	Reaped      int      `json:"reaped,omitempty"`
	Launch      string   `json:"launch,omitempty"`
	Malformed   int      `json:"malformed,omitempty"`
	Violation   string   `json:"violation,omitempty"`
	MaxInput    int      `json:"max_input,omitempty"`
	Limit       string   `json:"limit,omitempty"`
	ProviderRef string   `json:"provider_ref,omitempty"`
	Job         string   `json:"job,omitempty"`
	Slot        int      `json:"slot,omitempty"`
	Started     string   `json:"started,omitempty"`
	Ended       string   `json:"ended,omitempty"`
	End         string   `json:"end,omitempty"`
	RC          int      `json:"rc"`
	Notes       int      `json:"notes,omitempty"`
	Class       string   `json:"class,omitempty"`
	ReplacedBy  string   `json:"replaced_by,omitempty"`
	Verdict     *Verdict `json:"verdict,omitempty"`
}

Sidecar is everything the pool knows about a task that is not its text. It travels with the task file from pending/ to running/ to done/ or failed/.

func (Sidecar) BudgetWord

func (s Sidecar) BudgetWord() string

BudgetWord is what a RUN line prints for this task's token budget ceiling.

type SizeRound

type SizeRound struct {
	W               int
	Cores           int
	Load            float64
	CardsPerMin     float64
	PrevCardsPerMin float64
	Abstains        int
	Held            bool
}

SizeRound is one doubling round's measurements.

type SlotFile

type SlotFile struct {
	Job           string `json:"job"`
	JobDir        string `json:"job_dir,omitempty"`
	State         string `json:"state"`
	Pid           int    `json:"pid,omitempty"`
	Pgid          int    `json:"pgid,omitempty"`
	JobPgid       int    `json:"job_pgid,omitempty"`
	PidStarted    string `json:"pid_started,omitempty"`
	JobStarted    string `json:"job_started,omitempty"`
	RunnerPid     int    `json:"runner_pid"`
	RunnerStarted string `json:"runner_started,omitempty"`
	ReservedAt    string `json:"reserved_at,omitempty"`
	LaunchedAt    string `json:"launched_at,omitempty"`
	Nonce         string `json:"nonce"`
	ExitAttest    string `json:"exit_attest,omitempty"`
}

SlotFile is what <pool>/slots/<n>.json holds.

type SlotLease

type SlotLease struct {
	ID     string
	Owner  string
	Pid    int
	Label  string
	Until  time.Time
	Weight int    // 0 means 1: a lease written before weights
	Kind   string // optional card kind charged at admission
}

SlotLease is one bench slot lease: the directory name is the id.

func ListSlotLeases

func ListSlotLeases(store string, now time.Time) ([]SlotLease, error)

ListSlotLeases reads every lease in the store, in id order. A directory with no parseable lease file holds no lease and is skipped: it is a half-written take (Mkdir landed, the file never did), and take reaps it.

func (SlotLease) Line

func (l SlotLease) Line(now time.Time) string

Line is the `slots list` row for this lease.

func (SlotLease) State

func (l SlotLease) State(now time.Time) string

State reports live, expired or DRIFT at now: past until= with a live pid is DRIFT, past until= with a dead pid is expired, anything else is live. Stranded leases stay `live` on this field so a listing fill already knows (live, DRIFT, expired) still counts them as held.

func (SlotLease) Stranded

func (l SlotLease) Stranded(now time.Time) bool

Stranded reports a live-until lease whose holder is gone: the seat is still held, and list prints stranded=1 with the label so a manager can re-queue it.

func (SlotLease) Units

func (l SlotLease) Units() int

Units is how many share units this lease occupies. Missing or zero weight is 1.

type SlotWait

type SlotWait struct {
	Pending func() int
	Ask     func() bool
}

SlotWait is one slot's wait. Pending reports how many cards the slot's own queue holds and is the only read of that queue; Ask blocks on the coordinator's event (nova-pulse watch) and returns true when the event says to look again. Both are seams so a test drives them with a fake queue and a fake clock.

func (SlotWait) Wait

func (s SlotWait) Wait() int

Wait returns the number of cards the slot may take. With work already queued it returns it and never calls Ask. With an empty queue it calls Ask exactly once and reads the queue once more only when the event fired; it never loops on Pending.

type StageOptions

type StageOptions struct {
	Card      []byte
	TargetDir string // e.g. <jobDir>/repo
	JobDir    string // <jobDir>, where RESULT.md is written on timeout
	BenchHome string
	BenchName string
	Timeout   time.Duration
}

StageOptions describes a card staging request.

type StageResult

type StageResult struct {
	BaseRepo string
	BaseSha  string // the card's base sha; once staged, the full sha <job>/repo's HEAD is at
	Ref      string // the card's BASE: ref, checked out when it names no sha
	Branch   string // the branch the staged checkout is on (CardStageBranch)
	Mirror   string
	Staged   bool
	TimedOut bool
	Wall     time.Duration
}

StageResult is the outcome of a staging operation.

func StageCard

func StageCard(opts StageOptions) (StageResult, error)

StageCard stages the repository for a card into TargetDir using the bench mirror. The repo, sha and ref are ReadCardBase's (base-repo:, REPO:, or a clone URL; base-sha: or BASE:). If the card names no repo that can be read, staging is skipped and Staged is false; the caller refuses such a card when CardNamesRepo says it named one (#3711). The checkout is at the sha (else the ref, else the clone's default head) on the branch CardStageBranch names, so the card commits on a branch, not a detached HEAD. If base-repo is remote and no bench mirror is found, staging fails without contacting GitHub. Staging runs git clone --reference <mirror> and git fetch/checkout <base-sha> with a hard timeout (default 120s). If the timeout expires, it writes RESULT.md:

RESULT: BLOCKED stage-timeout <bench> <secs>

and returns ErrStageTimeout.

type StagingIdentity

type StagingIdentity struct {
	Owner string
	Name  string
	Email string
}

StagingIdentity is the pool's one identity row: the name and email every commit the worker makes must carry, and the owner that row belongs to.

func LoadPoolIdentity

func LoadPoolIdentity(poolDir string) (StagingIdentity, error)

LoadPoolIdentity reads the pool's identity.tsv: a header `owner\tname\temail` plus the pool's identity row. A pool with no identity row is refused: launching a job under nobody's name is how a commit ends up carrying whatever the bench's git config held. Every refusal ends with PoolIdentityRemedy.

type StartupCost

type StartupCost struct {
	HarnessSHA256       string
	FirstUsageCacheRead int
	FirstUsageTurns     int
	Present             bool
}

StartupCost is the harness's measured first turn: what the harness spends before the card says anything. Present is false when no file has been written yet, which accepts every budget.

func ReadStartupCost

func ReadStartupCost(root string) (StartupCost, error)

ReadStartupCost reads `<root>/startup-cost.tsv`. An absent file is not an error and answers Present=false: no measurement accepts every budget. A header line naming the columns is skipped.

type SuperviseInput

type SuperviseInput struct {
	Pool    *Pool
	Task    string
	Slot    int
	Nonce   string
	Worker  Worker
	Sidecar Sidecar
	Key     string
	// THE LAUNCH SEAM (docs/SPEC-SANDBOX.md, the dispatcher caller). Sandbox is the
	// resolved nova-sandbox binary the harness is wrapped in; an EMPTY one is rule 11's
	// one loud workaround, which the dispatcher has already announced for this job, and
	// it is reachable only from a `--no-sandbox` a person typed.
	Sandbox        string
	UsageInterval  time.Duration
	Stdout, Stderr io.Writer
	Now            func() time.Time
	// Sleep is the wait between two polls of the group the supervisor confirms dead. It
	// is a seam beside Now so a test can run the bounded drain and grace waits to their
	// ends without holding the machine's clock; nil is time.Sleep.
	Sleep func(time.Duration)
	// Term fires when this supervisor should end from outside (SIGTERM in the process
	// that runs `supervise`). Nil never fires. A closed or signalled channel reaps the
	// harness group and returns; default Go death of this process would leave that group.
	Term <-chan struct{}
}

SuperviseInput is everything `supervise` is handed.

type TaskDecision

type TaskDecision struct {
	Task       string
	Label      string
	Reason     string
	Confidence float64
	NeedsHuman float64
}

TaskDecision is one finished task's typed decision: the reason choice, its confidence, and the needs_human probability, behind the floor. It is what nova-pulse run reads to send a task to HUMAN or to retry it (card 8371).

func DecideFinished

func DecideFinished(p *Pool, do DecideFunc, floor float64, now func() time.Time) ([]TaskDecision, error)

DecideFinished asks the one typed decision for every finished task in the pool's done/ and failed/ directories that has not been decided already. It is the core of `nova-swarm triage --decide`, lifted into a function so nova-pulse run can call it after each harvest (card 8371). floor 0 takes DefaultDecideFloor.

type Timeline

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

Timeline is an io.Writer over the child's output: it timestamps the harness's own NOVA-TIMELINE report lines as they arrive and keeps one row per completed span. It is safe for the child's concurrent stdout and stderr copies; a partial line is held until its newline arrives.

func NewTimeline

func NewTimeline() *Timeline

NewTimeline returns an empty timeline recorder.

func (*Timeline) Rows

func (t *Timeline) Rows() []TimelineRow

Rows returns a copy of the spans observed so far, in report order.

func (*Timeline) Write

func (t *Timeline) Write(p []byte) (int, error)

Write accepts the child's output, one chunk at a time, and observes every complete line.

type TimelineRow

type TimelineRow struct {
	Start        time.Time
	End          time.Time
	Tool         string // `model` for a turn, else the tool name and command
	InputTokens  string // empty when the harness reported none
	OutputTokens string
}

TimelineRow is one observed event: a model turn or a tool call.

func ReadTimeline

func ReadTimeline(path string) ([]TimelineRow, error)

ReadTimeline reads one card's timeline.tsv, mapping its columns by the header so a reader is not wedded to a column's position. An absent file is an empty timeline and no error, which is how a job that reported nothing phases as nothing rather than failing the fold.

type TimeoutHierarchy

type TimeoutHierarchy struct {
	// CardTimeout is the overall execution budget for the entire card/task (5s..2h).
	CardTimeout time.Duration

	// StepTimeout is the per-step or per-turn execution budget (5s..CardTimeout).
	StepTimeout time.Duration

	// GitTimeout is the bounded budget for VCS/git sub-operations (5s..min(StepTimeout, 1m)).
	GitTimeout time.Duration
}

TimeoutHierarchy defines the multi-tier execution timeouts for card execution, enforcing invariants across all three layers:

CardTimeout >= StepTimeout >= GitTimeout

func DefaultTimeoutHierarchy

func DefaultTimeoutHierarchy() TimeoutHierarchy

DefaultTimeoutHierarchy returns the canonical hierarchy using default budgets.

func NewTimeoutHierarchy

func NewTimeoutHierarchy(card, step, git time.Duration) TimeoutHierarchy

NewTimeoutHierarchy creates a validated, bounded timeout hierarchy.

  • card is clamped to [MinExecutionTimeout, MaxExecutionTimeout] (5s..2h).
  • step is bounded to [MinExecutionTimeout, CardTimeout]. If step <= 0, it defaults to min(DefaultStepTimeout, CardTimeout) or CardTimeout if CardTimeout < DefaultStepTimeout.
  • git is bounded to [MinGitTimeout, min(StepTimeout, MaxGitTimeout)]. If git <= 0, it defaults to min(StepTimeout/3, MaxGitTimeout), floored at MinGitTimeout (or StepTimeout if StepTimeout < MinGitTimeout).

func (TimeoutHierarchy) CardContext

CardContext returns a context bounded by CardTimeout.

func (TimeoutHierarchy) GitBudget

func (h TimeoutHierarchy) GitBudget(remainingStep time.Duration) time.Duration

GitBudget computes the budget for a git operation given the remaining step duration. It follows the canonical 1/3 allocation rule (matching busBounds):

git := remainingStep / 3, capped at MaxGitTimeout (1m), floored at MinGitTimeout (5s).

It never exceeds remainingStep.

func (TimeoutHierarchy) GitContext

func (h TimeoutHierarchy) GitContext(parent context.Context, remainingStep time.Duration) (context.Context, context.CancelFunc)

GitContext returns a context bounded by GitBudget(remainingStep).

func (TimeoutHierarchy) StepBudget

func (h TimeoutHierarchy) StepBudget(remainingCard time.Duration) time.Duration

StepBudget computes the remaining budget for a step given the remaining card duration. If remainingCard is zero or negative, no budget remains. Otherwise, the budget is bounded by min(h.StepTimeout, remainingCard).

func (TimeoutHierarchy) StepContext

func (h TimeoutHierarchy) StepContext(parent context.Context, remainingCard time.Duration) (context.Context, context.CancelFunc)

StepContext returns a context bounded by StepBudget(remainingCard).

func (TimeoutHierarchy) Validate

func (h TimeoutHierarchy) Validate() error

Validate checks that the hierarchy satisfies the ordering invariants.

type ToolchainRoot

type ToolchainRoot struct {
	// Name is the token BOTH lists carry, in slash form. A name that begins with "/" is a
	// SYSTEM root and is that absolute directory -- or, when Tool is set, the versioned
	// prefix under which one directory is resolved; any other name is HOME-RELATIVE and is
	// joined to one bench home.
	Name string
	// Path is the absolute directory on one machine, resolved through its symlinks. It is
	// set only by ToolchainRoots and is empty in the declaration.
	Path string
	// Exec is the kind: true means `--read`, readable AND executable; false means
	// `--read-noexec`, readable and NOT executable.
	Exec bool
	// Tool, when set, is the program on the bench's PATH whose resolved launcher names the
	// ONE versioned directory under Name that this bench actually runs -- `go` resolving to
	// /opt/homebrew/Cellar/go/1.27.1/libexec/bin/go names /opt/homebrew/Cellar/go/1.27.1.
	// The version belongs to the machine, so naming the prefix itself would grant every
	// version ever brewed and hard-coding one would be stale on the next `brew upgrade`.
	Tool string
	// Optional says the provisioning standard does not DEMAND this root, only grants it when
	// the bench has it. A linux bench's roots are the standard's own installs and a missing
	// one is drift; a Mac bench's toolchains are installed by hand and by brew, so its roots
	// are reported and never drifted on -- a Mac with no .NET is a Mac with no .NET, and the
	// bench still has to have a Go, which the standard's own `go` check is what asserts.
	Optional bool
}

ToolchainRoot is one entry of the one list: a directory the bench's toolchain lives in and THE KIND OF GRANT it gets. The kind is the security decision, not a detail of the argv -- a `--read` root CARRIES EXECUTE on both bodies (landlock's read subset is EXECUTE|READ_FILE|READ_DIR, and the darwin profile grants process-exec* globally), so a tree the bench user can write to must be named as the read-only kind or a card can run whatever lands in it (Johnny's security read of #1364).

func ToolchainRootList

func ToolchainRootList(goos string) []ToolchainRoot

ToolchainRootList is the declaration itself for one operating system, kinds and all: what a reader of the wall and the class test in internal/ci ask when the question is not "which paths" but "granted HOW". An OS the list does not speak for names nothing at all, which is the behaviour of every run before the roots existed and never a guess at another OS's layout. A copy, so no caller can edit the one source by holding it.

func ToolchainRoots

func ToolchainRoots(goos, home string) []ToolchainRoot

ToolchainRoots is the wall's side: this machine's list, absolute, carrying each root's kind, and SKIPPED IF ABSENT. Absent is the machine's shape and not the caller's mistake -- a Mac bench has no `~/sdk` and may have no dotnet -- while rule 5 of the wall REFUSES a `--read` or a `--read-noexec` naming a path that is not there, so a root that does not exist must never reach the argv. An empty home names no HOME-relative root, because a relative root is a refusal and a root at the filesystem's top is not a toolchain; a system root needs no home and is still named.

Every path is RESOLVED THROUGH ITS SYMLINKS before it is named, because the grant is checked against the resolved target on both bodies: `/opt/homebrew/opt/openjdk` is itself a symlink into the Cellar, and granting the link would name a tree the card's `java` is not under.

func (ToolchainRoot) Home

func (r ToolchainRoot) Home() bool

Home says whether this root is found under a bench home rather than at an absolute path.

type TriageAction

type TriageAction string

TriageAction is the routing action determined by failure classification.

const (
	// ActionRetry means the card should be retried with backoff.
	ActionRetry TriageAction = "retry"

	// ActionQuarantine means the card is placed in queue/quarantine/<reason>/<card-id>.
	ActionQuarantine TriageAction = "quarantine"

	// ActionFail means the failure is treated as normal test failure.
	ActionFail TriageAction = "fail"
)

type TriageInput

type TriageInput struct {
	Pool           *Pool
	Batch          string
	Dirs           []string
	Since          string
	All            bool
	NoState        bool
	Max            int
	Owed           []string
	Stdout, Stderr io.Writer
	Now            func() time.Time

	// Decide asks TypeSafe Jev for one typed abstain reason per finished
	// task (reason choice + needs_human noul) behind Floor, and appends it
	// to each TRIAGE REPORT line. Below the floor the line says decide=?
	// and the pool's own class stands; a provider error keeps today's bare
	// line. KeyEnv names the environment variable holding the key
	// (JEV_API_KEY when empty); BaseURL overrides the endpoint, for the
	// httptest fake in tests.
	Decide    bool
	Floor     float64
	KeyEnv    string
	BaseURL   string
	UsagePath string
	// contains filtered or unexported fields
}

TriageInput is one triage run.

type TriageRouteResult

type TriageRouteResult struct {
	CardID      string       `json:"card_id"`
	Action      TriageAction `json:"action"`
	Reason      string       `json:"reason"`
	FailureKind FailureKind  `json:"failure_kind"`
	Attempts    int          `json:"attempts"`
	TargetDir   string       `json:"target_dir,omitempty"`
	Message     string       `json:"message"`
}

TriageRouteResult holds the result of triage routing.

func RouteFailure

func RouteFailure(queueDir, cardID string, cardContent []byte, attempts, maxAttempts int, fc FailureClassification) (TriageRouteResult, error)

RouteFailure routes a failed card according to the failure taxonomy:

  • CardDefect -> immediately quarantined under queue/quarantine/<reason>/<card-id>
  • TransientProvider -> retried if attempts < maxAttempts; quarantined if attempts >= maxAttempts
  • InfraCrash -> retried if transient, quarantined if exhausted/recurrent
  • TestFailure -> marked fail

type TriageState

type TriageState struct {
	Version   int               `json:"version"`
	Runs      int               `json:"runs"`
	LastISO   string            `json:"last_iso"`
	LastCount int               `json:"last_count"`
	Consumed  map[string]string `json:"consumed"`
}

TriageState is <pool>/triage.json: what has been folded into a page already.

type TrustState

type TrustState map[string]string

TrustState is the coordinator's per-kind state, keyed by kind: `trial`, `trusted` or `paused` (SPEC-TOOLWORK.md eligibility rule 1's TRUST listing).

func ReadTrustFixture

func ReadTrustFixture(path string) (TrustState, error)

ReadTrustFixture reads the coordinator's per-kind state from a file in the shape `nova-pulse trust` prints (SPEC-TOOLWORK.md eligibility rule 1):

TRUST kind=<kind> area=<area> state=<trial|trusted|paused> cards=<n>/<N> ...
TRUST OK kinds=<n> trial=<n> trusted=<n> paused=<n>

THE VERB DOES NOT EXIST YET. `nova-pulse trust` is the other half of T06a and belongs to the lane that owns `internal/pulse`; until it lands, the bench hands `lint --card` a fixture in exactly this shape with `--trust <file>`, and the day the verb ships its own stdout is the fixture. Lines that are not TRUST rows, and the TRUST OK summary, are read past, so a whole listing can be saved to a file and handed over unedited.

type UsageRow

type UsageRow map[string]string

UsageRow is one job's row.

func (UsageRow) Int

func (r UsageRow) Int(name string) (int, bool)

Int reads a numeric column, reporting whether it is a number at all -- a dash is not.

type Verdict

type Verdict struct {
	Who      string `json:"who"`
	Accurate int    `json:"accurate"`
	Wrong    int    `json:"wrong"`
}

Verdict is a reader's counts for one task, recorded by a person (rule 8).

type WallReader

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

WallReader is an io.Writer over the child's own output, put in the capture chain beside the log files and the timeline so every byte is seen AS IT ARRIVES rather than re-read from a file once the child is gone. It holds a partial line until its newline, the way Timeline does, and it is safe for the child's concurrent stdout and stderr copies.

It decides nothing. It remembers the first refusal, the last STEP the card reached, and whether the card made another tool call after the refusal -- and it calls announce ONCE, with the typed line, the first time a refusal appears.

func NewWallReader

func NewWallReader(task string, announce func(string)) *WallReader

NewWallReader returns a reader for one card. announce is called once, from the writing goroutine, with the typed line of the first refusal; a nil announce says nothing.

func (*WallReader) Step

func (r *WallReader) Step() string

Step is the last `STEP <n>` the card printed, or "" when it printed none.

func (*WallReader) Stopped

func (r *WallReader) Stopped() (WallRefusal, string, bool)

Stopped is the refusal that stopped this card -- a refusal with no tool call after it -- and false when there was no refusal or the card moved past the one there was.

func (*WallReader) Write

func (r *WallReader) Write(p []byte) (int, error)

Write consumes the child's output and never fails: a reader that could fail a write would break the MultiWriter it sits in and take the card's own log down with it.

type WallRefusal

type WallRefusal struct {
	Path string
	Step string
}

WallRefusal is one wall death: the path the wall refused and the last step the card reached. An empty Path or Step is the dash the report line writes.

func WallRefused

func WallRefused(log []byte) (WallRefusal, bool)

WallRefused reports the first path the harness's own fence or the OS wall refused in one log, and whether either refused anything. The first is the one that matters: the model stops at it, and every later line is a consequence of the same closed path.

func WallStopped

func WallStopped(log []byte) (WallRefusal, bool)

WallStopped is the refusal that STOPPED a card, which is not the same question WallRefused answers. WallRefused reports the FIRST refusal in a log whether or not the card survived it; this reports one only when the card made no further tool call after it.

The difference is a whole card. `js-under-20-bytes` took a refusal in the harness's startup banner, ran sixteen more model steps, and died in a provider stall twenty minutes later; WallRefused named the banner and the shift went looking at the wall.

type WindowsKillStrategy

type WindowsKillStrategy int

WindowsKillStrategy specifies the termination mechanism on Windows: taskkill vs direct syscall.

const (
	// StrategyTaskkill uses taskkill.exe /T (/F) to terminate the full process tree.
	StrategyTaskkill WindowsKillStrategy = iota

	// StrategySyscall uses direct Win32 syscall (TerminateProcess / os.Process.Kill) on the PID.
	StrategySyscall
)

func (WindowsKillStrategy) String

func (s WindowsKillStrategy) String() string

String returns the human-readable name of the strategy.

type Worker

type Worker struct {
	Name     string `json:"name"`
	Provider string `json:"provider"`
	Model    string `json:"model"`
	BaseURL  string `json:"base_url,omitempty"`
	EnvVar   string `json:"env_var"`
	KeyFile  string `json:"key_file"`
	// SECRET (issue #881): the NAME of the environment variable that holds the key in
	// THIS process's own environment, delivered by `nova-secrets exec` around the run
	// (docs/SPEC-SECRETS.md, the second caller). It is the replacement for `key_file`
	// when the key is sealed once and delivered at use and NEVER a file on disk. Exactly
	// one of `key_file` and `secret` is set; run and supervise require that variable to
	// be present and non-empty, and the value is never written to a file, never printed,
	// and never in a RUN or SUPERVISE line.
	Secret string `json:"secret,omitempty"`
	Usage  string `json:"usage"`
	// CLASS (CARD-8390): `public` means this worker never sees private source
	// (the public-class gate); `paid` is the default. Empty decodes as paid so
	// no description written before the gate changes meaning by being re-read.
	//
	// CLASS (worker check): `class` is the one word public or paid, an OPTIONAL
	// field a description may carry and the launcher never requires. `nova-swarm
	// worker check` validates it when it is present. The two budgets below are
	// the same fields worker check validates.
	Class       string   `json:"class,omitempty"`
	Harness     string   `json:"harness"`
	HarnessArgs []string `json:"harness_args,omitempty"`
	WorkerDir   string   `json:"worker_dir"`
	Deadline    string   `json:"deadline"`
	Board       string   `json:"board,omitempty"`
	// READ ROOTS: the one field the wall added (docs/SPEC-SANDBOX.md, "there is no --root
	// flag"). Every job runs inside nova-sandbox, whose read set is the OS and toolchain
	// roots plus what the caller names; a toolchain installed into a USER directory -- Go
	// under ~/go, node under ~/.nvm, the Studio's /Users/<user>/toolchains -- is under no
	// system root, so a harness that needs one dies inside the wall and runs outside it.
	// It is OPTIONAL, it is a list of absolute existing directories, and it is READ-ONLY:
	// the write set is the job's own and is never configurable from a file. A worker that
	// needs nothing beyond the system roots names nothing here.
	ReadRoots []string `json:"read_roots,omitempty"`

	// CARD BUDGET (CARD-8317): the optional per-card stop. A runaway card is a
	// prompt defect, not a model one: max_turns caps the harness output log's
	// assistant turns and max_cache_read caps the observed cache_read, and run
	// stops the card at either with end=budget and a PROMPT-DEFECT line. Zero
	// is unset; negative is refused at load.
	MaxTurns     int `json:"max_turns,omitempty"`
	MaxCacheRead int `json:"max_cache_read,omitempty"`

	// PROVIDER PHRASES: the second optional field, and it is the TRIAGE LINE'S (#103). A
	// job that dies because the request did not fit is its own failure class, and the only
	// evidence of it is a sentence the provider printed -- in the provider's own words.
	// OpenCode says `Rate limit reached: input token limit exceeded`; the tool carries a
	// table of the sentences it has met (InputLimitPhrases), and a description may ADD the
	// one its provider uses. It never SUBSTITUTES: a caller teaching the tool their
	// provider's words cannot silently un-teach it another's.
	InputLimitPhrases []string `json:"input_limit_phrases,omitempty"`

	// LAUNCH GRACE (issue #900): how long a harness may run before its exit stops
	// counting as a LAUNCH failure. Nine of forty Muse requests died in under two
	// seconds with a provider 5xx and wasted the slot they held; a death inside this
	// window whose tail names a provider server error is a launch that did not take,
	// and the dispatcher retries it instead of filing it. OPTIONAL: the default is
	// DefaultLaunchGrace (15s). A slow failure -- one that takes longer than this -- is
	// a real run that failed and is never retried.
	LaunchGrace string `json:"launch_grace,omitempty"`

	// KEEP DATA (issue #1048): a worker description that sets keep_data=true asks `run` not
	// to reap the finished slots' data/ and tmp/ at task end. It is a person's debugging
	// switch: the default is false, and the shared per-bench caches under <root>/cache are
	// never touched either way.
	KeepData bool `json:"keep_data,omitempty"`
}

Worker is a worker description, decoded strictly: an unknown field is a refusal, because a misspelled field in a file that names a key's location is a silent default.

func LoadWorker

func LoadWorker(path string) (Worker, []error)

LoadWorker reads and checks a worker description, reporting EVERY independent problem in one run (ONBOARDING point 2) rather than sending a first run back once per field.

func (Worker) BudgetRefusal

func (w Worker) BudgetRefusal(s StartupCost) string

BudgetRefusal is the one line a card budget below the measured startup cost is refused with, or "" when the budget stands or no measurement exists. A max_cache_read below twice the measured first cache read cannot leave room for the card; a max_turns below the measured first turns plus two stops the card before its first real turn. The refusal names both numbers.

func (Worker) DataHome

func (w Worker) DataHome(slot int, id string) string

DataHome is the job's own data home, exported as XDG_DATA_HOME so the harness keeps its own database there, one per job. It is the whole reason slots exist.

func (Worker) DefaultDeadline

func (w Worker) DefaultDeadline() time.Duration

DefaultDeadline is the worker description's own, which add --deadline overrides per task.

func (Worker) HarnessConfig

func (w Worker) HarnessConfig() []byte

HarnessConfig is the config this tool writes for the harness, and it carries the environment variable's NAME, never its value: a value written here would be a key at rest in a directory nobody treats as a secret store. It is rewritten at every start, so a stale provider declaration cannot outlive a worker description.

func (Worker) HasCardBudget

func (w Worker) HasCardBudget() bool

HasCardBudget reports whether the description carries any card budget.

func (Worker) IsPublic

func (w Worker) IsPublic() bool

IsPublic reports whether this worker may see public source only.

func (Worker) JobDir

func (w Worker) JobDir(slot int, id string) string

JobDir is a slot's job directory for one task.

func (Worker) OverCardBudget

func (w Worker) OverCardBudget(u ProviderUsage, turns int) (over bool, cacheRead, max int)

OverCardBudget reports whether the observed usage and turn count exceed the description's card budget. It returns the observed cache_read (0 for an absence, which never fires) and the budget that fired: max_cache_read where the cache fired, else max_turns.

func (Worker) RefreshSlot

func (w Worker) RefreshSlot(slot int) error

RefreshSlot copies the home worker directory into the slot directory, ONE WAY. It is a copy rather than a mount or a link: a worker that could write back into the home copy could change the next worker's self, and the next worker would load it without anybody reading the change. The jobs/ subtree is left alone, because it is the slot's own work and not the home copy's.

func (Worker) SlotDir

func (w Worker) SlotDir(slot int) string

SlotDir is a slot's own working directory, <worker-dir>-<slot>.

func (Worker) WriteHarnessConfig

func (w Worker) WriteHarnessConfig(slot int) (string, error)

WriteHarnessConfig writes that config into the slot directory at every start.

type WorkerProfile

type WorkerProfile struct {
	Name              string
	Usage             string
	Harness           string
	HarnessArgs       []string
	WorkerDir         string
	Deadline          string
	ReadRoots         []string
	InputLimitPhrases []string
}

Jump to

Keyboard shortcuts

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