Documentation
¶
Index ¶
- Constants
- func ActiveDir(workDir string) string
- func AppendProviderCall(workDir string, row ProviderCallRow) (int64, error)
- func AttemptToken(t *pm.Task) string
- func ClaimsLiveRun(status string, r *pausereason.Reason) bool
- func ClearTaskKill(workDir string, taskID int) error
- func DBPath(workDir string) string
- func EventHistory(workDir string, q statedb.HistoryQuery) (statedb.HistoryPage, error)
- func EventHistoryRow(workDir string, isStep bool, n int64) (row statedb.HistoryRow, ok bool, err error)
- func EventHistoryTop(workDir string) (statedb.HistoryCursor, error)
- func LegacyPath(workDir string) string
- func LogEvent(workDir string, row EventRow)
- func LogEventDetails(workDir string, row EventRow, v any)
- func MigrateLegacy(workDir string) (bool, error)
- func ReleaseQuarantine(workDir string, taskID int, actor, via string) (*pm.TaskQuarantine, error)
- func RequestAdoption(workDir string, r *runbuild.Request) error
- func RequestTaskKill(workDir string, taskID int, targetStatus, requestedBy, attempt string) error
- func SelfBuild() runbuild.Build
- func SetCommitPolicy(workDir string, p *pm.CommitPolicy) error
- func SetFollowBuilds(workDir string, on bool) error
- func SetReviewGate(workDir string, g *pm.ReviewGate) error
- func StateDBPath(workdir string) string
- func StatePath(workdir string) string
- type EventRow
- type EventType
- type KillRequest
- type ProjectState
- func (s *ProjectState) AddStep(result StepResult)
- func (s *ProjectState) ClaimsLiveRun() bool
- func (s *ProjectState) LastNSteps(n int) []StepResult
- func (s *ProjectState) LiveDefaultMaxMinutes() int
- func (s *ProjectState) LiveEffort() string
- func (s *ProjectState) LiveMaxParallel() int
- func (s *ProjectState) PausedFor(code pausereason.Code) bool
- func (s *ProjectState) RequireTask(id int) (*pm.Task, error)
- func (s *ProjectState) Save() error
- func (s *ProjectState) SaveDirect() error
- func (s *ProjectState) SaveRunStatus() error
- func (s *ProjectState) SetPaused(r pausereason.Reason)
- func (s *ProjectState) SyncFromDisk()
- type ProviderCallRow
- type StepResult
Constants ¶
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.
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 ¶
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 ¶
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 ¶
ClearTaskKill removes the kill row for taskID. Idempotent.
func DBPath ¶
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
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 ¶
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 ¶
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
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
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
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 ¶
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
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
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 ¶
StateDBPath returns the SQLite database path for the given workdir. It does NOT resolve the active session; use Load for that.
Types ¶
type EventRow ¶
EventRow is re-exported for callers that don't want to import statedb directly (the orchestrator uses pkg/state already).
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.