state

package
v0.0.5 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Index

Constants

View Source
const (
	EventSessionStarted    = statedb.EventSessionStarted
	EventSessionPaused     = statedb.EventSessionPaused
	EventSessionFailed     = statedb.EventSessionFailed
	EventPlanComplete      = statedb.EventPlanComplete
	EventTaskStarted       = statedb.EventTaskStarted
	EventTaskDone          = statedb.EventTaskDone
	EventTaskFailed        = statedb.EventTaskFailed
	EventTaskSkipped       = statedb.EventTaskSkipped
	EventTaskHeal          = statedb.EventTaskHeal
	EventTaskKilled        = statedb.EventTaskKilled
	EventTaskAdded         = statedb.EventTaskAdded
	EventTaskAddedExternal = statedb.EventTaskAddedExternal
	EventTaskDeleted       = statedb.EventTaskDeleted
	EventTaskStatusChange  = statedb.EventTaskStatusChange
	EventTaskBackground    = statedb.EventTaskBackground
	EventTaskReview        = statedb.EventTaskReview
	EventTaskAborted       = statedb.EventTaskAborted
	EventTaskInterrupted   = statedb.EventTaskInterrupted
	EventEvolveRoundStart  = statedb.EventEvolveRoundStart
	EventEvolveDiscovered  = statedb.EventEvolveDiscovered
	EventEvolveNoOp        = statedb.EventEvolveNoOp
	EventResourceCeiling   = statedb.EventResourceCeiling
	EventProjectSeed       = statedb.EventProjectSeed
	EventProjectResult     = statedb.EventProjectResult
	EventFeatureCreated    = statedb.EventFeatureCreated
	EventFeatureRemoved    = statedb.EventFeatureRemoved
	EventFeaturePR         = statedb.EventFeaturePR
	EventWriteBack         = statedb.EventWriteBack
	EventFirewall          = statedb.EventFirewall
	EventCredentialRefresh = statedb.EventCredentialRefresh
	EventCredentialRefused = statedb.EventCredentialRefused
	EventEgress            = statedb.EventEgress
	EventFailover          = statedb.EventFailover
	EventDiskLimit         = statedb.EventDiskLimit
	EventRunReexecuted     = statedb.EventRunReexecuted
	EventRunAdoption       = statedb.EventRunAdoption
)

Re-exported event-type constants. Keep this list in sync with pkg/statedb/events.go.

View Source
const NoStep = statedb.NoStep

NoStep is the EventRow.Step value for events not bound to any step. Steps are 0-based, so 0 is a real step number — callers must set Step to NoStep explicitly when an event has no associated step.

Variables

This section is empty.

Functions

func ActiveDir

func ActiveDir(workDir string) string

ActiveDir resolves the effective working directory for state operations. If a session is active (recorded in .cloop/active_session), it returns the session's isolated directory; otherwise it returns workDir unchanged.

The name is read from a file the project's own workload can write, and the hub reads and writes the directory it resolves to on that project's behalf (Task 20418). So a name is honoured only when it is one a session can have — `cloop session` allows letters, digits, hyphens and underscores — and the directory it names is a real one under .cloop/sessions, not a link. A name with a separator or "..", or a sessions directory that is a link, would point the hub at another project's database, or the control plane's; the project's own state is used instead.

func AppendProviderCall

func AppendProviderCall(workDir string, row ProviderCallRow) (int64, error)

AppendProviderCall persists one provider-call audit row to the project's state.db. Returns the assigned id on success. Returns (0, nil) when no project exists at workDir (so observability code can fan out to every project without bothering to check first).

func AttemptToken

func AttemptToken(t *pm.Task) string

AttemptToken renders a task's StartedAt as the attempt identifier used to scope kill requests. Returns "" for a task that has never been started, which the poller treats as naming no live execution.

Both sides of the comparison go through this function so the two never drift on formatting; the poller additionally compares parsed instants so a row written by a differently-formatting client still matches.

func ClaimsLiveRun added in v0.0.5

func ClaimsLiveRun(status string, r *pausereason.Reason) bool

ClaimsLiveRun reports whether a stored status says a run is in flight: running, evolving, or paused for a reason the run waits out itself — a disk_low pause, whose process stays up and resumes on its own (Task 20381, pausereason.RunWaits).

It is a claim, not a fact: the run writes the status, and a run killed before it could clear it leaves the claim behind. Comparing the claim with what is actually executing is how the hub finds such a run — which is why a waiting pause has to count here: one killed while it waited would otherwise say "waiting for disk space" forever.

func ClearTaskKill

func ClearTaskKill(workDir string, taskID int) error

ClearTaskKill removes the kill row for taskID. Idempotent.

func DBPath

func DBPath(workDir string) string

DBPath returns the path to the state.db for the given workdir, accounting for active session directories. Other packages (e.g. pkg/eventlog) call this so they don't need to duplicate the active-session resolution logic.

func EventHistory added in v0.0.5

func EventHistory(workDir string, q statedb.HistoryQuery) (statedb.HistoryPage, error)

EventHistory reads one page of the project's journal — its steps and events as one feed, newest first — through a single database handle (Task 20384). It never loads the project: a page reads the rows it shows and no others. A project with no state database is statedb.ErrProjectNotFound.

func EventHistoryRow added in v0.0.5

func EventHistoryRow(workDir string, isStep bool, n int64) (row statedb.HistoryRow, ok bool, err error)

EventHistoryRow returns one journal row whole — step n when isStep, else event n — or ok=false when there is no such row.

func EventHistoryTop added in v0.0.5

func EventHistoryTop(workDir string) (statedb.HistoryCursor, error)

EventHistoryTop returns the newest step and event in the project's journal.

func LegacyPath added in v0.0.5

func LegacyPath(workDir string) string

LegacyPath returns where workDir's legacy state.json is — the active session's, when one is active: the file Load and MigrateLegacy convert.

func LogEvent

func LogEvent(workDir string, row EventRow)

LogEvent appends one row to the project's event journal. Best-effort: any persistence error is reported to stderr and otherwise swallowed so that observability never blocks the orchestrator's work.

Pass workDir as the project root (or a session dir); it is resolved through the same effectiveDBPath the rest of the package uses. The Timestamp field of row will be set to time.Now() if zero. Events without an associated step must set row.Step to NoStep — steps are 0-based, so 0 is a real step number.

func LogEventDetails

func LogEventDetails(workDir string, row EventRow, v any)

LogEventDetails serialises v as the row's Details JSON before recording. v may be nil (no details). Marshal failures fall back to recording without details rather than dropping the event entirely.

func MigrateLegacy added in v0.0.5

func MigrateLegacy(workDir string) (bool, error)

MigrateLegacy converts workDir's legacy state.json — the active session's, when one is active — into its database, under exactly the conditions Load converts on, and reports whether it did. It is how `cloop migrate` converts a legacy project: through statedb, like every other write of project state, rather than through a schema of its own (Task 20374).

func ReleaseQuarantine added in v0.0.5

func ReleaseQuarantine(workDir string, taskID int, actor, via string) (*pm.TaskQuarantine, error)

ReleaseQuarantine clears a task's quarantine mark and the node losses recorded against it, as part of an explicit reset to pending. actor is who reset it and via names the path ("cli", "ui", "bulk").

It returns the mark it released, or nil when the task carried none — in which case the task's losses, if it had any, are still cleared: a reset is a fresh start, and a loss left over from before it would quarantine the task on the next unrelated node failure. A project with no database has nothing to release.

func RequestAdoption added in v0.0.5

func RequestAdoption(workDir string, r *runbuild.Request) error

RequestAdoption files a one-shot request for the project's run to adopt a newer build at its next task boundary (Task 20389). The request names the run's process, so it is acted on by that run or by none.

func RequestTaskKill

func RequestTaskKill(workDir string, taskID int, targetStatus, requestedBy, attempt string) error

RequestTaskKill records a manual-abort request for taskID. The orchestrator's fast-tick poller picks this up and fires the task's registered cancel; once the worker exits, target_status is re-applied so the operator's choice wins over the worker's "canceled → failed" handling.

attempt names the execution being aborted — the RFC3339Nano started_at the caller observed on the task, via AttemptToken. Requests are scoped to that attempt so one filed against an execution that has since ended cannot cancel a later attempt of the same task (Task 20203).

Returns nil on success or when the project DB does not exist (a kill request against a project with no orchestrator running is benign — there's nothing to cancel).

func SelfBuild added in v0.0.5

func SelfBuild() runbuild.Build

SelfBuild is this binary's build, with the newest schema it embeds.

func SetCommitPolicy added in v0.0.5

func SetCommitPolicy(workDir string, p *pm.CommitPolicy) error

SetCommitPolicy stores a project's "done means committed" setting (Task 20370) without rewriting anything else in its state. A run in progress picks the change up at its next sync, and checks the next task it starts with it.

func SetFollowBuilds added in v0.0.5

func SetFollowBuilds(workDir string, on bool) error

SetFollowBuilds stores a project's "follow new builds" option (Task 20389) without rewriting anything else in its state. A run in progress picks the change up at its next sync, which is its next task boundary.

func SetReviewGate added in v0.0.5

func SetReviewGate(workDir string, g *pm.ReviewGate) error

SetReviewGate stores a project's review gate settings (Task 20357) without rewriting anything else in its state. A run in progress picks the change up at its next sync.

func StateDBPath

func StateDBPath(workdir string) string

StateDBPath returns the SQLite database path for the given workdir. It does NOT resolve the active session; use Load for that.

func StatePath

func StatePath(workdir string) string

StatePath returns the legacy JSON state file path (used for migration detection).

Types

type EventRow

type EventRow = statedb.EventRow

EventRow is re-exported for callers that don't want to import statedb directly (the orchestrator uses pkg/state already).

func ListEvents

func ListEvents(workDir string, offset, limit int) ([]EventRow, int, error)

ListEvents returns up to limit events starting at offset, latest-first, along with the total event count. Returns (nil, 0, nil) for projects that don't have a state.db yet.

type EventType

type EventType = statedb.EventType

EventType is re-exported for the same reason.

type KillRequest

type KillRequest = statedb.KillRequest

KillRequest is re-exported from statedb so callers don't have to import the lower layer just to read the requestor / target_status.

func LookupTaskKill

func LookupTaskKill(workDir string, taskID int) (KillRequest, bool, error)

LookupTaskKill returns the pending kill row for taskID. Used by the orchestrator after a worker exits to recover the operator's chosen target_status.

func PendingKills

func PendingKills(workDir string) ([]KillRequest, error)

PendingKills returns all currently-pending abort requests for the project. Returns (nil, nil) when the project has no DB yet.

type ProjectState

type ProjectState struct {
	Goal        string `json:"goal"`
	WorkDir     string `json:"workdir"`
	MaxSteps    int    `json:"max_steps"`
	CurrentStep int    `json:"current_step"`
	Status      string `json:"status"` // running, complete, failed, paused, evolving
	// PauseReason explains a "paused" status: which of the ~26 conditions
	// stopped the run, and — for a subscription cap — when it lifts. Nil
	// for every other status; the invariant is enforced in toRaw/fromRaw
	// rather than at the call sites. Always set via SetPaused.
	PauseReason  *pausereason.Reason `json:"pause_reason,omitempty"`
	Steps        []StepResult        `json:"steps"`
	CreatedAt    time.Time           `json:"created_at"`
	UpdatedAt    time.Time           `json:"updated_at"`
	Model        string              `json:"model,omitempty"`
	Instructions string              `json:"instructions,omitempty"`
	AutoEvolve   bool                `json:"auto_evolve"`
	EvolveStep   int                 `json:"evolve_step"`

	// Provider settings
	Provider string `json:"provider,omitempty"`

	// Effort is the persisted model reasoning-effort level (low, medium,
	// high, xhigh, max; empty = provider default). Set at init time or from
	// the Web UI's Provider & Model picker; honored by providers that
	// support it (currently claudecode via the claude CLI --effort flag).
	Effort string `json:"effort,omitempty"`

	// Product manager mode
	PMMode     bool                   `json:"pm_mode,omitempty"`
	Plan       *pm.Plan               `json:"plan,omitempty"`
	Milestones []*milestone.Milestone `json:"milestones,omitempty"`

	// Cumulative token usage across all steps
	TotalInputTokens  int `json:"total_input_tokens,omitempty"`
	TotalOutputTokens int `json:"total_output_tokens,omitempty"`

	// DefaultMaxMinutes is the project-level per-task execution time budget.
	DefaultMaxMinutes int `json:"default_max_minutes,omitempty"`

	// SkipClarify persists the --skip-clarify flag.
	SkipClarify bool `json:"skip_clarify,omitempty"`

	// InnovateMode persists the --innovate flag from the most recent run.
	InnovateMode bool `json:"innovate_mode,omitempty"`

	// Parallel persists the --parallel flag (PM mode runs dependency-ready
	// tasks concurrently in each round). Read by cmd/run as a default.
	Parallel bool `json:"parallel,omitempty"`

	// MaxParallel caps the worker pool when Parallel is true. 0 = unlimited.
	MaxParallel int `json:"max_parallel,omitempty"`

	// WorktreeParallel persists the --worktree-parallel flag. When true and the
	// project is a git repo, parallel-mode tasks run inside dedicated git
	// worktrees at .cloop/worktrees/task-<id>/ and their branches are drained
	// back into the base branch through a serialized merge queue.
	WorktreeParallel bool `json:"worktree_parallel,omitempty"`

	// PlanOnly persists the --plan-only flag (PM mode: decompose goal into
	// tasks but do not execute them).
	PlanOnly bool `json:"plan_only,omitempty"`

	// RetryFailed persists the --retry-failed flag (reset failed tasks to
	// pending before running).
	RetryFailed bool `json:"retry_failed,omitempty"`

	// DryRun persists the --dry-run flag (show prompts without invoking the
	// provider).
	DryRun bool `json:"dry_run,omitempty"`

	// ReviewGate configures the optional reviewer that checks each task's
	// changes before cloop pushes or merges them (Task 20357). Nil or disabled
	// means no review. Set from the dashboard or `cloop review gate`; a
	// running orchestrator picks up changes at its next sync.
	ReviewGate *pm.ReviewGate `json:"review_gate,omitempty"`

	// CommitPolicy is the opt-in "done means committed" post-condition (Task
	// 20370): with it on, a task whose agent says it is done is accepted only
	// once the changes it made are committed — and, with Pushed, on their
	// branch's upstream. Nil or disabled means the agent's word stands. Set
	// from the dashboard, `cloop run --require-committed[=pushed]` or `cloop
	// require-committed`; a running orchestrator picks up changes at its next
	// sync.
	CommitPolicy *pm.CommitPolicy `json:"commit_policy,omitempty"`

	// FollowBuilds is the "follow new builds" option (Task 20389): a run of
	// this project on this host adopts a newly deployed cloop build at its
	// next task boundary instead of running the build it started with until
	// it ends. Set from the dashboard or `cloop run --follow-builds`, stored
	// only by SetFollowBuilds; a running orchestrator picks it up at its next
	// sync. Off by default.
	FollowBuilds bool `json:"follow_builds,omitempty"`

	// RunOwner is the record the project's run keeps of its own process and
	// build (Task 20389), written only by the run; AdoptRequest a pending
	// "adopt the hub's build at the next task boundary", filed by
	// RequestAdoption and cleared by the run that acts on it.
	RunOwner     *runbuild.Owner   `json:"run_owner,omitempty"`
	AdoptRequest *runbuild.Request `json:"adopt_request,omitempty"`

	// RunBuild is RunOwner as the process reading it sees it — compared with
	// its own build and checked for liveness — so the dashboard and `cloop
	// status` can say "running build X, N builds behind". Computed when the
	// state is read, never stored.
	RunBuild *runbuild.Status `json:"run_build,omitempty"`

	// StepCount is the total number of step rows in the database. Always
	// populated: Load mirrors len(Steps); LoadLite fills it from a cheap
	// SELECT COUNT(*) while Steps is nil. Read it instead of len(Steps)
	// when working with lite-loaded state (Task 20125).
	StepCount int `json:"-"`

	// LastStepTime is the timestamp of the most recent step row. Same
	// semantics as StepCount: set by both Load (last element) and LoadLite
	// (cheap lookup of the newest step row); used by health probes to detect stalled
	// runs without scanning the Steps slice.
	LastStepTime time.Time `json:"-"`
	// contains filtered or unexported fields
}

func Init

func Init(workdir, goal string, maxSteps int) (*ProjectState, error)

Init creates a new project state and persists it.

func Load

func Load(workdir string) (*ProjectState, error)

Load reads project state, resolving the active session first. Auto-migrates from state.json on first run.

func LoadDatabase added in v0.0.3

func LoadDatabase(dbPath string) (*ProjectState, error)

LoadDatabase reads the project state held in exactly the database file at dbPath, and nothing else.

Load and LoadFromDir both decide *which* database to read from files in the project directory — an active_session pointer, a session.json marker, a legacy state.json that may be migrated over the database first. That is right for a project the caller owns. It is wrong for one a sandboxed workload has just finished writing to, which is the one caller this exists for (pkg/executor/projectseed's Harvest): every one of those files is the workload's to plant, and following them would let it choose what the device reports — or, through a legacy file newer than the database, overwrite the run's results with a stale plan before they were read.

A missing file is reported as statedb.ErrProjectNotFound so callers can tell "the run left no database" from "the database is unreadable".

func LoadFromDir

func LoadFromDir(dir string) (*ProjectState, error)

LoadFromDir loads state directly from a specific directory (e.g. a session directory) without going through the active-session resolution in Load. dir may be a session dir (state.db lives flat inside) or a regular workDir (.cloop/state.db). Returns nil, nil if no state exists.

func LoadLite

func LoadLite(workdir string) (*ProjectState, error)

LoadLite is like Load but skips reading the per-step rows. The returned ProjectState has Steps == nil while StepCount and LastStepTime are populated from cheap SQL aggregates (Task 20125). Use it from handlers that render counts or check last-activity but never touch step Output — for a 5k-step project this saves ~1.5 MiB of allocation per call.

func (*ProjectState) AddStep

func (s *ProjectState) AddStep(result StepResult)

func (*ProjectState) ClaimsLiveRun added in v0.0.5

func (s *ProjectState) ClaimsLiveRun() bool

ClaimsLiveRun is the package function for this state.

func (*ProjectState) LastNSteps

func (s *ProjectState) LastNSteps(n int) []StepResult

func (*ProjectState) LiveDefaultMaxMinutes

func (s *ProjectState) LiveDefaultMaxMinutes() int

LiveDefaultMaxMinutes returns DefaultMaxMinutes under the live-toggle lock.

func (*ProjectState) LiveEffort

func (s *ProjectState) LiveEffort() string

LiveEffort returns Effort under the live-toggle lock. Use from goroutines that can run concurrently with Save/SyncFromDisk (parallel workers, pollers).

func (*ProjectState) LiveMaxParallel

func (s *ProjectState) LiveMaxParallel() int

LiveMaxParallel returns MaxParallel under the live-toggle lock.

func (*ProjectState) PausedFor added in v0.0.2

func (s *ProjectState) PausedFor(code pausereason.Code) bool

PausedFor reports whether the run is paused for the given reason code.

func (*ProjectState) RequireTask

func (s *ProjectState) RequireTask(id int) (*pm.Task, error)

RequireTask returns the task with the given ID, wrapping statedb.ErrTaskNotFound when the plan is missing or no task matches. HTTP handlers should use errors.Is(err, statedb.ErrTaskNotFound) and statedb.HTTPStatus(err) to map to the correct status code.

func (*ProjectState) Save

func (s *ProjectState) Save() error

Save writes the project state to the SQLite store, first merging any tasks that were added externally while this state object was in memory. This is the standard save path used by the orchestrator. UI handlers that intentionally mutate (add/edit/delete) tasks should use SaveDirect to avoid the merge re-adding deleted tasks.

func (*ProjectState) SaveDirect

func (s *ProjectState) SaveDirect() error

SaveDirect writes the project state to the SQLite store WITHOUT merging externally-added tasks. Use this for intentional UI mutations (add, edit, delete tasks) where the caller has already loaded the latest state and does not want deleted tasks re-added from disk.

func (*ProjectState) SaveRunStatus added in v0.0.5

func (s *ProjectState) SaveRunStatus() error

SaveRunStatus writes the run's status and pause reason and nothing else — no tasks, no steps, no merge (Task 20362). It is the last write an orchestrator makes when it stops because a full Save failed, so it touches as little as it can: the plan on disk stays as the last successful save left it. It never creates a database; a project without one has nowhere to record a status.

func (*ProjectState) SetPaused added in v0.0.2

func (s *ProjectState) SetPaused(r pausereason.Reason)

SetPaused parks the run and records why.

This is the only sanctioned way to reach the "paused" status: a direct `s.Status = "paused"` is rejected by TestPausedStatusAlwaysCarriesAReason in tests/arch, because a pause without a reason is exactly the silent stall this field exists to end. Callers that genuinely have nothing to say should still pick a code — pausereason.CodeIdle is the ordinary end of a run.

func (*ProjectState) SyncFromDisk

func (s *ProjectState) SyncFromDisk()

SyncFromDisk re-reads the on-disk state and merges externally-added tasks.

This is the read side of the merge, so it also adopts the queue order from disk: the caller is a running orchestrator about to decide what to execute next, and another process may have reordered the queue since it last looked.

type ProviderCallRow

type ProviderCallRow = statedb.ProviderCallRow

ProviderCallRow is re-exported so callers don't have to import statedb directly.

func ListProviderCalls

func ListProviderCalls(workDir string, offset, limit, taskID int, providerFilter string) ([]ProviderCallRow, int, error)

ListProviderCalls returns up to limit rows from the audit log starting at offset, latest-first. Returns (nil, 0, nil) for projects that have no state.db yet.

func LoadProviderCall

func LoadProviderCall(workDir string, id int64) (*ProviderCallRow, error)

LoadProviderCall returns the full row for a single audit-log id.

type StepResult

type StepResult struct {
	Step         int       `json:"step"`
	Task         string    `json:"task"`
	Output       string    `json:"output"`
	ExitCode     int       `json:"exit_code"`
	Duration     string    `json:"duration"`
	Time         time.Time `json:"time"`
	InputTokens  int       `json:"input_tokens,omitempty"`
	OutputTokens int       `json:"output_tokens,omitempty"`
}

Jump to

Keyboard shortcuts

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