artifact

package
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Sep 27, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package artifact persists the full AI response for each PM task as a human-readable Markdown file with YAML frontmatter under .cloop/tasks/. It also stores shell verification scripts and their results.

Index

Constants

MaxReadBytes is the cap applied to every read of a task artifact. It is the shared constant described in boundedread; see there for why artifacts need one despite not being attacker-controlled.

View Source
const SandboxRunFile = ".cloop/sandbox-run.json"

SandboxRunFile is where the current run's environment record lives, relative to the project directory.

Variables

This section is empty.

Functions

func HeadTruncationNotice

func HeadTruncationNotice(total int64) string

HeadTruncationNotice is the marker to append to a head-biased read that hit the cap. It goes at the bottom, where the content stops.

func HumanBytes

func HumanBytes(n int64) string

HumanBytes renders a byte count for a human reading a log line or a prompt. Exported so callers that impose their own, tighter budget on top of the read cap can describe it in the same units.

func LiveArtifactDir

func LiveArtifactDir(workDir string) string

LiveArtifactDir returns the directory where live streaming artifacts are written.

func LiveArtifactPath

func LiveArtifactPath(workDir string, taskID int) string

LiveArtifactPath returns the canonical path for the live streaming output file for the given task ID. The file is written incrementally during execution.

func NewRunID added in v0.0.2

func NewRunID() string

NewRunID mints an identifier for one execution.

Random rather than derived from a counter or a timestamp: run ids are compared across hubs and across restarts, and the two obvious derivations both collide exactly where it hurts. A counter restarts at zero when the hub does, and a timestamp collides when two projects are dispatched in the same instant — which is the normal case on a hub running a fleet.

Falls back to a time-derived value only if the system entropy source fails, which on Linux means the process is already in serious trouble. A colliding id is still better than an empty one here: an empty run id would make every unattributable run join to every other.

func OpenLiveArtifact

func OpenLiveArtifact(workDir string, taskID int) (*os.File, error)

OpenLiveArtifact creates (or truncates) the live streaming output file for a task and returns the open file handle. The caller is responsible for closing it. Errors are non-fatal; the caller should treat nil as "no live artifact".

func ReadArtifactHead

func ReadArtifactHead(workDir, artifactPath string) (data []byte, truncated bool, total int64, err error)

ReadArtifactHead reads at most MaxReadBytes from the start of a task artifact. Prefer ReadArtifactTail unless the caller genuinely wants the beginning — a browsable detail pane that renders the first N lines, for instance, where the reader scrolls from the top.

func ReadArtifactTail

func ReadArtifactTail(workDir, artifactPath string) (data []byte, truncated bool, total int64, err error)

ReadArtifactTail reads at most MaxReadBytes from the end of a task artifact.

This is the right default for anything that wants to know how a task turned out — context injection into the next task, evaluation, summarization — because an agent's conclusion, its completion signal and any failure it hit are all at the end of its transcript. total is the artifact's full size on disk, so callers can say how much they did not read.

func ReadLiveArtifactTail

func ReadLiveArtifactTail(workDir string, taskID int) (data []byte, truncated bool, total int64, err error)

ReadLiveArtifactTail reads at most MaxReadBytes from the end of a task's live streaming output file.

Live artifacts are the riskiest of the lot: they are the log of a task that is running right now, so they are the ones actually growing while something reads them. Always tail-biased — a live log is read to find out where the task has got to, which is by definition its last line.

func ReadTaskOutput

func ReadTaskOutput(workDir string, task *pm.Task) string

ReadTaskOutput reads the AI output for a completed task. If task.ArtifactPath is set and readable, it returns the artifact body with YAML frontmatter stripped. Falls back to task.Result if the file is missing. Returns empty string when neither source is available.

The read is bounded at MaxReadBytes and biased to the tail, because every caller of this function wants the outcome: chained task input, acceptance checks, post-task evaluation, replay comparison and the task detail panel. When the cap is hit the returned string opens with TailTruncationNotice so no consumer mistakes the surviving tail for the whole transcript.

func TailTruncationNotice

func TailTruncationNotice(total int64) string

TailTruncationNotice is the marker to prepend to a tail-biased read that hit the cap. It goes at the top because that is where the omission is: the reader is about to see the end of a much longer log.

Truncation must always be stated. A summarizer that silently receives 16 MiB of a 2 GB log reports confidently on a fraction of the work; one that is told the log was cut can qualify what it says.

func WriteExecArtifact

func WriteExecArtifact(workDir string, task *pm.Task, cmdArgs []string, exitCode int, elapsed time.Duration, output string) (string, error)

WriteExecArtifact persists the output of a 'cloop task exec' run under .cloop/tasks/<id>-<slug>-exec-<ts>.md. Returns the relative path (relative to workDir) of the artifact file.

func WriteSandboxRun

func WriteSandboxRun(workDir string, rec SandboxRecord) (string, error)

WriteSandboxRun persists the record for the run that is about to start.

It is called by the control plane, from outside the sandbox, with the project directory the workload will see. Returns the path written.

func WriteTaskArtifact

func WriteTaskArtifact(workDir string, task *pm.Task, fullOutput string) (string, error)

WriteTaskArtifact writes the full AI response for a completed task to .cloop/tasks/<id>-<slug>.md with YAML frontmatter. Returns the relative path (relative to workDir) of the artifact file.

func WriteVerificationArtifact

func WriteVerificationArtifact(workDir string, task *pm.Task, script, scriptOutput string, passed bool) (string, error)

WriteVerificationArtifact persists a shell verification script and its execution result under .cloop/tasks/<id>-<slug>-verify.md. Returns the relative path (relative to workDir) of the artifact file.

Types

type SandboxRecord

type SandboxRecord struct {
	// ExecutorID and ExecutorKind identify where it ran.
	ExecutorID   string `json:"executor_id,omitempty"`
	ExecutorKind string `json:"executor_kind,omitempty"`
	// Isolation is the boundary strength the executor advertised at dispatch:
	// "none" | "container" | "vm" | "remote" (executor.Capabilities.Isolation).
	//
	// Recorded here rather than looked up from the executor later because it
	// is a statement about this run: a driver reconfigured afterwards, or an
	// id re-enrolled against a different backend, would otherwise rewrite the
	// history of every task it ever ran. Empty on records written before
	// Task 20244 — readers infer it from ExecutorKind, which has always been
	// written, rather than reading the absence as "no isolation".
	Isolation string `json:"isolation,omitempty"`
	// SpecHash is the .cloop/sandbox.yaml content hash, or empty when the
	// project has no spec and ran on the executor's defaults.
	SpecHash string `json:"spec_sha256,omitempty"`
	// RequestedImage is the reference the spec asked for, before resolution.
	RequestedImage string `json:"requested_image,omitempty"`
	// PinnedImage is the reference the workload actually ran from —
	// digest-pinned where the driver could resolve one.
	PinnedImage string `json:"pinned_image,omitempty"`
	// SetupHash identifies the setup: commands baked into a derived image,
	// empty when the spec has none.
	SetupHash string `json:"setup_sha256,omitempty"`
	// Warnings are the clamps applied while parsing the spec.
	Warnings []string `json:"warnings,omitempty"`
	// StartedAt is when the workload began.
	StartedAt time.Time `json:"started_at,omitempty"`

	// Identity is who asked for this run — an oidcauth OwnerKey, the owning
	// project's registry Owner, or cost.IdentityLocal when nobody
	// authenticated (Task 20264). It rides this record for the same reason
	// the executor fields do: the hub knows it and the orchestrator needs it,
	// and the project directory is the only channel that crosses the sandbox
	// boundary in that direction.
	//
	// The orchestrator stamps it onto every cost ledger row so a report can
	// answer "what did alice spend today". It is *not* what the hub's quota
	// enforcement books against — see pkg/ui/spend.go — because this file
	// lands in a directory the workload can write to, and a workload that
	// could name the payer could drain a colleague's budget.
	Identity string `json:"identity,omitempty"`

	// RunID names this execution (Task 20282). It is minted by the hub before
	// anything is acquired for the run, so the same token can be stamped on the
	// secret-lease rows the broker writes, on this record, and — through it —
	// on the task.dispatch and task.finish rows the orchestrator and the state
	// store emit.
	//
	// It exists because a task id is not a key. Task 63 may run five times, and
	// "which leases did task 63 hold" has five different answers; joining lease
	// rows to a task id silently unions them. A run id makes the join exact:
	// leases attach to the execution that spent them.
	//
	// Empty for a bare `cloop run`, which the orchestrator fills in with a run
	// id of its own so that even an unmanaged run has a correlatable trace.
	RunID string `json:"run_id,omitempty"`

	// LeaseIDs are the secret leases handed to this run, in the order the
	// broker issued them.
	//
	// Identifiers only — never material, and structurally incapable of carrying
	// it: a lease id is an opaque token the broker mints, and the payload it
	// refers to never enters this package's scope. They ride the record for the
	// same reason the executor fields do: the hub knows them and the
	// orchestrator, which writes the dispatch row, cannot see them from inside
	// the sandbox.
	//
	// The authoritative binding is still the broker's own secret.lease rows,
	// which carry the same run id and which a workload cannot write. These are
	// the convenience copy that makes a dispatch row readable on its own; a
	// reader who needs to defend the join against a hostile workload should use
	// the run id and read the broker's rows.
	LeaseIDs []string `json:"lease_ids,omitempty"`
}

SandboxRecord is the provenance of one run's execution environment.

func LoadSandboxRun

func LoadSandboxRun(workDir string) (SandboxRecord, bool)

LoadSandboxRun reads the record for the current run, if there is one.

Every failure returns ok=false rather than an error. A missing, truncated or malformed record must never be the reason a completed task's output is not persisted — the stamp is provenance, and losing provenance is strictly better than losing the work it describes.

func ParseSandboxFrontmatter

func ParseSandboxFrontmatter(s string) (SandboxRecord, bool)

ParseSandboxFrontmatter extracts the sandbox stamp from a task artifact's leading YAML frontmatter block.

It is deliberately a hand-rolled scan of `key: value` lines rather than a YAML decode. The block is written by frontmatter above one line at a time, the keys are a closed set, and a partial read that lands mid-block must yield the keys it did see rather than a parse error for the truncated last line — which is what a real YAML parser would give.

func ReadTaskSandbox

func ReadTaskSandbox(workDir string, task *pm.Task) (SandboxRecord, bool)

ReadTaskSandbox recovers the sandbox a past task actually ran in.

Why this exists alongside LoadSandboxRun

They read the same record from two different places, and only one of them is durable. LoadSandboxRun reads .cloop/sandbox-run.json, which the control plane overwrites before every run — it answers "what am I running in right now", and by the time anyone asks about task 42 it describes some later task. The per-task copy stamped into the artifact's frontmatter is the one that survives, so it is the one a reproduction has to read.

Returns ok=false for a task with no artifact, no frontmatter, or no sandbox stamp — the ordinary case for a run that predates the stamp or executed on the host. A caller must treat that as "unknown", never as "unsandboxed".

func (SandboxRecord) IsZero

func (r SandboxRecord) IsZero() bool

IsZero reports whether the record carries nothing worth stamping.

RunID counts. A hub-dispatched run on a driver with no image and no sandbox spec still has a run id, and that id is the join key the whole audit trail hangs on — treating such a record as empty would drop it at the LoadSandboxRun guard and leave the task rows with nothing to correlate against the lease rows the same dispatch wrote.

func (SandboxRecord) Pinned

func (r SandboxRecord) Pinned() bool

Pinned reports whether the recorded image is immutable — whether re-running this record would provably get the same filesystem.

The test is for "@sha256:" rather than for any digest-shaped substring: a tag may legitimately contain hex, and calling a tag pinned is the one error here that would matter, because it is the claim someone relies on when they say a result reproduces.

Jump to

Keyboard shortcuts

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