When to use this / when not to use this
Use this when
- The tool handles source, prompts, credentials, build evidence, or other data that should remain locally owned.
- Core capture, inspection, verification, or export must survive offline use and hosted-service failure.
Do not use this when
- A central service is the legitimate authority for shared state, access control, or real-time collaboration.
- The product cannot define conflict resolution, migration, backup, and deletion semantics for local data.
Decide what the local machine is authoritative for
A tool is not local-first merely because it has a CLI. If every useful command uploads source, requires a hosted account, or stores the only readable history remotely, the terminal is a thin client. A local-first developer tool creates its primary artifact on the user's machine in a documented format and keeps core inspection available offline. Sources: Ink & Switch local-first software research.
Write down the authority split. The local machine can own session evidence, configuration, indexes, and private keys; a remote service can own organization policy, team discovery, or publication. When the two disagree, the product should have an explicit reconciliation rule instead of whichever response arrived last winning silently.
A practical local-first promise
- Core capture, inspection, verification, and export work without network access.
- Artifacts live in a documented path and portable, versioned format.
- Uninstalling the binary does not make existing artifacts unreadable.
- Remote publication is optional and visibly names what will leave the machine.
- The user can delete local data without an online control plane.
Separate immutable artifacts, mutable indexes, and caches
Durable evidence and rebuildable convenience data should not share the same failure mode. Store completed receipts, reports, or scan results as immutable artifacts. Keep lookup indexes and last-used pointers in a small transactional database or atomic metadata files. Put downloaded toolchains and derived renderings in a cache that can be deleted at any time. Sources: SQLite transactional guarantees, XDG Base Directory Specification.
Content-addressed artifact names reduce accidental duplication and make integrity checks cheap. They do not replace lifecycle metadata: keep schema version, media type, size, creation context, and retention class beside each digest. For mutable state, write to a temporary file, fsync when durability matters, and rename atomically. Sources: OCI Content Descriptor specification.
State layout
- Artifacts: immutable receipts, patches, reports, exports, and signatures.
- Index: session lookup, labels, current schema, and artifact references.
- Config: explicit user preferences with source and precedence.
- Secrets: operating-system keychain or restricted files, never general config JSON.
- Cache: downloads and derived data that a repair command can recreate.
Core commands read and write local portable artifacts; adapters publish copies or policy results without owning the original record.
Source: AgentReceipt repository- 01WorkspaceSource + instructions + tool state
- 02Local coreCapture, scan, verify, migrate
- 03ArtifactsImmutable portable bundles
- 04IndexMutable, rebuildable lookup
- 05AdaptersCI, PR, dashboard, policy sync
Design for two terminals and a killed process
Developer tools are invoked by humans, editors, hooks, and agents at the same time. Use a per-repository or per-session lock with owner metadata and a bounded stale-lock recovery rule. Avoid a single global lock that turns unrelated repositories into one failure domain.
Assume the process can stop between every pair of writes. Build multi-step operations around a staging directory or journal, then publish one final pointer atomically. On startup, distinguish incomplete staging data from complete artifacts; offer a repair or discard action instead of treating partial files as valid history. Sources: SQLite atomic commit and crash recovery.
Crash tests
- Kill the process after artifact write but before index update.
- Run two sessions in the same repository and in two different repositories.
- Fill the disk during finalization and confirm the previous index still opens.
- Interrupt a schema migration and rerun it.
- Leave a lock behind, then test stale-owner detection without deleting a live lock.
Add remote adapters after the local contract is stable
CI, pull requests, team dashboards, and hosted policy are valuable, but each should consume the same export that a local verifier can read. The adapter uploads an explicit bundle, records the remote object identifier locally, and never mutates the signed artifact to add synchronization metadata.
Queue outbound work when offline and use idempotency keys derived from the artifact identity. Authentication failure should block publication, not local capture or inspection. This keeps service outages from turning a development tool into an availability dependency while still supporting organizational workflows. Sources: Stripe idempotent request documentation.
Remote boundary checks
- Preview the files, fields, and redactions that will be uploaded.
- Use separate scopes for read, publish, and policy administration.
- Persist idempotency keys and remote identifiers outside signed artifacts.
- Retry transport failures without recollecting mutable local inputs.
- Keep local verification available when the remote API is down.
A local-first artifact architecture
This diagram separates authoritative bytes from rebuildable views and optional coordination. The boundary is drawn from the storage patterns shared by AgentReceipt, Skills Doctor, and RitualAI.
Authoritative
Survives restart and remains inspectable without a service
- Atomic artifact files
- Canonical manifests
- Signatures + digests
Derived
Can be deleted and rebuilt from authoritative artifacts
- SQLite indexes
- Search projections
- Rendered reports
Coordination
Improves sharing without becoming the local write authority
- Sync cursor
- Remote object store
- Publication API
- Write temp file → fsync → atomic rename → publish manifest reference
- Read artifact → validate digest → update rebuildable index
- Queue remote sync after local commit; retain the local result when sync fails
Choosing the authority boundary
This comparison records the tradeoffs I encountered building file-backed developer tools. The choice is not file versus database in the abstract; it is which representation a user can recover and inspect when every helper process is gone.
| Criterion | Artifact files | Embedded database | Remote service |
|---|---|---|---|
| Offline write path | Complete | Complete | Unavailable without a queue |
| Human inspection | Direct with ordinary tools | Requires query tooling | Requires API access |
| Query flexibility | Low until indexed | High locally | High but network-bound |
| Recovery model | Copy and verify bytes | Restore or repair database | Provider-specific export |
| Role in this architecture | Authority | Rebuildable projection | Optional coordination |
Implementation examples
Concrete commands and data shapes you can adapt.
func atomicWrite(path string, data []byte, mode fs.FileMode) error {
dir := filepath.Dir(path)
tmp, err := os.CreateTemp(dir, ".pending-*")
if err != nil { return err }
defer os.Remove(tmp.Name())
if err := tmp.Chmod(mode); err != nil { return err }
if _, err := tmp.Write(data); err != nil { return err }
if err := tmp.Sync(); err != nil { return err }
if err := tmp.Close(); err != nil { return err }
return os.Rename(tmp.Name(), path)
}.tool/
├── artifacts/sha256/ # immutable, portable
├── index.sqlite # mutable, rebuildable
├── config.json # user-owned preferences
├── staging/ # incomplete operations
└── cache/ # safe to deleteDecision log
The choices that shape the design—and what each choice costs.
- Make artifacts the integration boundary
Local commands, CI, and hosted adapters can share a stable format without sharing process internals.
Tradeoff: The artifact schema becomes a public API that needs versioning and compatibility tests. - Treat indexes as rebuildable
A corrupted lookup database should not destroy the evidence or source material it points to.
Tradeoff: Rebuild commands need enough metadata in each artifact and may take longer on large histories. - Keep synchronization metadata outside signed bundles
Publishing to a second destination does not change the identity or integrity of the original artifact.
Tradeoff: Users must back up both artifacts and optional local labels or remote mappings if they need the full convenience state.
Failure cases
What breaks, how it presents, and the recovery boundary.
- A crash exposes a half-written artifact
- Signal
The index points to a file whose digest or footer is incomplete.
- Response
Write in staging, verify the digest, atomically rename, and update the index only after publication succeeds.
- Two processes finalize the same session
- Signal
Competing writers produce different manifests or overwrite the current pointer.
- Response
Use a session-scoped lock and immutable result names; reject the second finalization unless it is byte-identical.
- Remote auth failure blocks local work
- Signal
Capture or inspection exits because a token is expired or the service is unavailable.
- Response
Queue publication separately and keep local commands independent of remote authentication.
Repositories and primary references
Read the implementation, specifications, and tool documentation behind the article.
- AgentReceipt repositoryA Go CLI that keeps session evidence and verification local by default.
- Skills Doctor repositoryA TypeScript CLI that discovers and audits local instruction packages before optional repair.
- RitualAI repositoryA local-first agent workflow with explicit history and document storage decisions.
Related projects
Inspect the systems that ground this guide in implementation work.
- Go · CLI · AI ToolingAgentReceiptLocal-first Go CLI for recording AI coding sessions and producing verifier-ready replay evidence.
- TypeScript · CLI · AI ToolingSkills DoctorTypeScript CLI for auditing Claude/Codex Agent Skills for quality, structure, scoring, and repair readiness.
- TypeScript · CLI · AI WorkflowsRitualAITypeScript CLI that scans local Claude/Codex prompt history and turns repeated workflows into reusable skills.
Last updated: