Implementation Plan: Phase 3 — Lock Files
Context
Phase 3 adds lock files (.fullsend/lock.yaml) that pin all resolved remote dependencies for reproducible harness execution. This was implemented alongside Phases 1 and 2.
Implementation
Lock file package (internal/lock/lock.go)
LockFilestruct: version, generated_at, harnesses mapHarnessLock: source, sha256, resolved_at, dependenciesDependencyEntry: field, url, sha256, type, fetched_at, transitive_deps, filestypeis"file"for agents/policies or"directory"for skillsfileslists the manifest of files in directory dependencies (skills only)
Load(path): reads and validates lock fileSave(path, lf): atomic write with temp-file-then-renameLookup(harnessName): returns entry or nilIsStale(sourceHash): checks if harness has changedLookupDep(url): depth-first search through dependency tree
Lock file schema
yaml
# .fullsend/lock.yaml
version: 1
generated_at: "2026-05-12T14:30:00Z"
harnesses:
code:
source: harness/code.yaml
sha256: abc123...
resolved_at: "2026-05-12T14:30:00Z"
dependencies:
- field: agent
url: https://raw.githubusercontent.com/fullsend-ai/library/8cd3799.../agents/code.md
sha256: def456...
type: file
fetched_at: "2026-05-12T14:29:55Z"
- field: skills[0]
url: https://github.com/fullsend-ai/library/tree/8cd3799.../skills/cargo-check
sha256: <tree-hash>...
type: directory
fetched_at: "2026-05-12T14:29:56Z"
files:
- path: SKILL.md
sha256: abc123...
- path: scripts/check.sh
sha256: def456...
transitive_deps:
- field: skills[dep0]
url: https://raw.githubusercontent.com/fullsend-ai/library/8cd3799.../policies/rust-sandbox.yaml
sha256: jkl012...
type: file
fetched_at: "2026-05-12T14:29:57Z"CLI lock command (internal/cli/lock.go)
fullsend lock <agent-name> --fullsend-dir <dir>resolves all deps and writes lock file--updateflag forces re-resolution even if entry is current- Supports
--offline,--max-depth,--max-resourcesflags
Lock file resolution (internal/cli/lock.go:resolveFromLock)
- For each pinned dependency, verifies content exists in local cache
- For
type: "file"entries: usesCacheGetand returnscontentpath - For
type: "directory"entries: usesCacheGetDirand returnstree/path - For
field: "base"entries: verifies cache presence only (base composition is already resolved byLoadWithBasebeforeresolveFromLockruns) - Applies mutations to harness only after all deps are confirmed in cache
- Falls back to normal network resolution on failure
Integration in fullsend run (internal/cli/run.go)
- Checks for lock file before resolving
- If lock entry exists and is not stale, uses
resolveFromLock - If lock entry is stale, warns user to run
fullsend lock - Falls back to normal resolution if lock resolution fails
Verification
fullsend lock code --fullsend-dir .fullsendgenerates lock filefullsend run codeuses lock file when available- Modifying harness triggers stale warning
- Missing cache entries produce clear error messages
--updateforces re-resolution- Lock file correctly records
type: "directory"andfilesmanifest for skill dependencies - Lock file correctly records
type: "file"for agent and policy dependencies
