Artifact 5494de96e7938332345cf03f7d84d98575d2d5d60a31ecb1bbb9979e4b7ead96:
- File
CLAUDE.md
— part of check-in
[f894e896c1]
at
2026-05-04 16:50:06
on branch trunk
— chore(agents): scaffold ## Agent skills section + docs/agents/ (#107)
Adds the per-repo configuration that the engineering skills (triage, to-issues, to-prd, diagnose, tdd, improve-codebase-architecture) assume:
- Issue tracker mapping → docs/agents/issue-tracker.md (GitHub via gh CLI) - Triage label vocabulary → docs/agents/triage-labels.md (canonical names; needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix) - Domain doc layout → docs/agents/domain.md (single-context; ADRs live in docs/architecture/, no CONTEXT.md so skills fall back to CLAUDE.md + MEMORY.md)
CLAUDE.md gets an ## Agent skills block pointing at the three docs.
The four missing triage labels (needs-triage, needs-info, ready-for-agent, ready-for-human) were created on the GitHub repo at the same time. (user: [email protected], size: 11639) [annotate] [blame] [check-ins using]
EdgeSync
Fossil sync engine — replace Fossil's HTTP sync with NATS messaging or direct peer-to-peer. Leaf agents act as both sync clients and servers.
See MEMORY.md for detailed project history, patterns, and decisions across sessions.
See docs/architecture/ for condensed architectural decision records (ARDs):
core-library.md— libfossil package architecture, blob format, xfer wire format, SQLite driverssync-protocol.md— xfer card protocol, client/server flow, clone, UV, config sync, private artifactsagent-deployment.md— leaf agent, bridge, Docker, WASM targets, observabilitycheckout-merge.md— checkout/checkin, merge strategies, fork prevention, autosync, ci-lockrepo-operations.md— CLI, tags, FTS, verify/rebuild, auth, shun/purgetesting-strategy.md— test tiers, DST, sim, interop, BUGGIFYnotify-messaging.md— bidirectional messaging: data model, dual delivery, CLI, planned phases
Architecture
- Leaf Agent (Go): daemon that reads/writes Fossil SQLite repo DB directly. Syncs via NATS (client) and serves via HTTP or NATS (server). Can replace both client and server roles.
- Bridge (Go): translates between NATS messages and Fossil's HTTP /xfer card protocol. Optional — only needed to talk to an unmodified Fossil server.
Local-Only Directories
fossil/ and libfossil/ are upstream C reference checkouts, gitignored (local-only). They exist on disk for porting reference but are NOT tracked in git.
Build & Test
make build # Build edgesync, leaf, bridge binaries into bin/
make test # Run CI-level tests + sim serve tests (~15s)
make setup-hooks # Install pre-commit hook (~8s before each commit)
go build -buildvcs=false ./cmd/edgesync/ # Dual VCS needs -buildvcs=false
CI: GitHub Actions (.github/workflows/ci.yml) runs on push to main and PRs. Steps: go vet, unit tests (leaf + bridge in parallel), CLI build, CLI tests. Uses GOWORK=off. All Go deps are public — no private-module auth needed.
Go Modules (go.work)
Four modules in a workspace:
.(root) — hostscmd/edgesync/,sim/, soak runnerleaf/— leaf agent modulebridge/— bridge moduledst/— deterministic simulation tests- External dependency:
github.com/danmestas/libfossilv0.1.x (public, standalone repo)
For local libfossil development, copy go.work.example to go.work and clone libfossil at ../libfossil.
Important: libfossil hides all implementation packages under internal/. EdgeSync imports only the root package handle API (libfossil.Repo, libfossil.Transport, etc.), plus db/, simio/, and testutil/ which remain public.
Project Structure
Entry Points
cmd/edgesync/— Unified CLI binary (embeds libfossil'scli.RepoCmd+ EdgeSync-specific commands)leaf/cmd/leaf/— Standalone leaf agent daemonbridge/cmd/bridge/— Standalone bridge daemonsim/cmd/soak/— Continuous soak test runner
Core Library: libfossil (external: github.com/danmestas/libfossil v0.1.x)
libfossil exposes an opaque Repo handle — all operations are methods on it. Implementation packages (blob, content, manifest, sync, xfer, etc.) are under internal/ and not importable.
Public API surface:
| Symbol | Purpose |
|---|---|
libfossil.Create(), Open(), Clone() |
Repo constructors |
r.Commit(), r.Timeline(), r.ListFiles() |
Checkout operations |
r.Sync(), r.HandleSync(), r.XferHandler() |
Client + server sync |
r.CreateCheckout(), r.OpenCheckout() |
Working directory management |
r.Tag(), r.UVWrite/Read/List() |
Tags, unversioned files |
r.CreateUser(), r.ListUsers(), r.Config() |
Admin |
libfossil.Transport |
Interface — RoundTrip(ctx, []byte) ([]byte, error) |
libfossil.SyncObserver, CheckoutObserver |
Telemetry hooks |
libfossil.NewHTTPTransport() |
Built-in HTTP transport |
libfossil.NopSyncObserver(), StdoutSyncObserver() |
Built-in observers |
Public packages (still importable):
db/— SQLite adapter,db/driver/modernc,db/driver/ncrucessimio/— Clock, Rand, Env for deterministic testingtestutil/— Test helperscli/— Embeddable kong CLI commands (38 Fossil-compatible subcommands)observer/otel/— Optional OTel observer (separate go.mod)
EdgeSync-specific commands (not in libfossil):
sync start/sync now— NATS agent daemonbridge serve— NATS-to-HTTP bridgedoctor— Environment health check
Agent/Bridge
leaf/agent/—Config(config.go),New(),Start(),Stop(),SyncNow()nats_mesh.go—NATSMeshmodule: embedded NATS server + iroh tunnel establishmentserve_http.go— composes mux with/healthz+r.XferHandler()(operational endpoints live here, not in libfossil)serve_nats.go— NATS request/reply listener for leaf-to-leaf sync- Config fields:
NATSRole(peer/hub/leaf),NATSUpstream(optional external NATS),ServeHTTPAddr,ServeNATSEnabled,IrohEnabled,IrohPeers,IrohKeyPath - CLI flags:
--repo,--nats,--nats-client-port,--nats-role,--poll,--serve-http,--serve-nats,--uv,--push,--pull,--user,--password,--iroh,--iroh-peer,--iroh-key
bridge/bridge/—Config(config.go),New(),Start(),Stop()
Notify Messaging
leaf/agent/notify/— Bidirectional messaging for human-in-the-loop AImessage.go— Message, Action, Priority types;NewMessage(),NewReply()store.go— Free functions on*libfossil.Repo:InitNotifyRepo,CommitMessage,ReadMessage,ListThreads,ReadThreadpubsub.go—Publish()free function,Subscribertype (dedup, wildcard)notify.go—Service(Send, Watch, FormatWatchLine)
cmd/edgesync/notify.go— 7 CLI commands: init, send, ask, watch, threads, log, status- NATS subjects:
notify.<project>.<thread-short>(separate from sync subjects) - Storage: dedicated
notify.fossilrepo, managed by libfossil (nofossilbinary)
Simulation Testing
dst/— Deterministic single-threaded sim.SimNetwork(bridge mode),PeerNetwork(leaf-to-leaf).MockFossildelegates toHandleSyncWithOpts.sim/— Integration sim (real NATS + TCP fault proxy + real Fossil). Serve tests verifyfossil clone/fossil syncagainst ServeHTTP.- Both share
simio/abstractions andsync.BuggifyCheckerinterface
Observability (OpenTelemetry)
The leaf agent and sim tests emit traces, metrics, and logs via OpenTelemetry. Telemetry is optional — everything builds and runs without it. When OTEL_EXPORTER_OTLP_ENDPOINT is unset, the OTel observer is nil and the nopObserver (zero-cost) is used.
Secrets via Doppler
OTel export secrets (Honeycomb API key) are managed by Doppler. No .env files in the repo.
# First time per device:
brew install doppler
doppler login
doppler setup # links this directory to edgesync/dev
# Run with telemetry:
doppler run -- go test ./sim/ -run TestSimulation -v
doppler run -- make sim-full
# Run WITHOUT telemetry (no Doppler needed):
go test ./sim/ -run TestSimulation -v # works fine, just no OTel export
make test # CI tests never need Doppler
Observer Pattern
libfossil.SyncObserver and libfossil.CheckoutObserver interfaces — lifecycle hooks for sync sessions, rounds, errors, server-side handling, and checkout operations. leaf/telemetry/observer.go implements them with OTel spans and metrics. libfossil's root package has no OTel dependency — the optional observer/otel/ sub-module provides an OTel implementation with its own go.mod.
Honeycomb Dashboard
- Dataset:
edgesync-simintestenvironment - Board: "EdgeSync Sim — Operational Overview"
Deployment (Hetzner VPS)
Docker Compose stack in deploy/. Two containers: NATS + leaf agent.
# On VPS (203.0.113.1):
cd ~/EdgeSync/deploy && sudo docker compose up -d --build
# Update:
cd ~/EdgeSync && git pull && cd deploy && sudo docker compose up -d --build
| Endpoint | URL | Access |
|---|---|---|
| Public HTTPS | https://sync.example.com |
Cloudflare Tunnel |
| Tailscale HTTP | http://100.64.0.1:9000 |
Tailnet only |
| Tailscale NATS | nats://100.64.0.1:4222 |
Tailnet only |
deploy/Dockerfile— multi-stage build, usesGOWORK=off(copies leaf/, depends on published libfossil)deploy/docker-compose.yml— NATS on Tailscale IP, leaf on port 9000 (8080/8090 occupied by Coolify/Caddy)- Cloudflare Tunnel config:
~/.cloudflared/config.ymlon VPS, servicecloudflared-fossil - Repo files:
deploy/data/*.fossil(host volume mount)
Key Conventions
- SQLite drivers:
go build(modernc),-tags ncruces. OrEDGESYNC_SQLITE_DRIVERenv var. - Fossil blob format:
[4-byte BE uncompressed size][zlib data].Compress()andDecompress()handle this. - xfer wire format: Raw zlib (no prefix).
xfer.Decodeauto-detects: raw zlib → prefix+zlib → uncompressed. - SHA3 UUIDs: 64-char = SHA3-256 (Fossil 2.0+), 40-char = SHA1 (legacy)
- Auth: Empty
User= no login card = unauthenticated "nobody" sync - simio.CryptoRand{}: Use for production callsites of
repo.Createanddb.SeedConfig - Pre-commit hook:
make setup-hooksinstalls ~8s test gate - HandleSync vs HandleSyncWithOpts: Use
r.HandleSync()in production,r.HandleSyncWithOpts(ctx, req, HandleOpts{Buggify})for DST - libfossil is transport-agnostic: No operational endpoints (healthz, metrics) in libfossil. Use
XferHandler()to compose custom muxes. Operational concerns live inleaf/agent/serve_http.go.
Fossil Sync Protocol
- Transport: HTTP POST to
/xfer,application/x-fossil, zlib-compressed - Wire format: newline-separated cards (
command arg1 arg2) - Core cards: login, push, pull, file, cfile, igot, gimme, cookie, clone, clone_seqno, reqconfig, config
- UV cards: uvfile (NAME MTIME HASH SIZE FLAGS), uvigot (NAME MTIME HASH SIZE), uvgimme (NAME), pragma uv-hash
- Stateless request/response rounds that repeat until convergence
- Push/pull cards accept 1 arg (server-code only) when syncing with known remote
- UV sync:
SyncOpts{UV: true}, mtime-wins conflict resolution,pragma uv-hashshort-circuits when catalogs match
Fossil Repo Schema (core tables)
blob— content-addressed blobs (rid, uuid, size, content)delta— delta relationships (rid -> srcid)event— checkin manifestsmlink— file mappings per checkinunversioned— mutable UV files (name, mtime, hash, sz, encoding, content)
Agent skills
Issue tracker
Issues live as GitHub issues on danmestas/EdgeSync (use the gh CLI). See docs/agents/issue-tracker.md.
Triage labels
Canonical label vocabulary — needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix — plus bug/enhancement. See docs/agents/triage-labels.md.
Domain docs
Single-context layout. ADRs live in docs/architecture/ (not docs/adr/); no CONTEXT.md — fall back to CLAUDE.md + MEMORY.md. See docs/agents/domain.md.
GitNexus — Code Intelligence
This project is indexed by GitNexus. Tools (query, context, impact, rename, detect_changes, cypher) are available via MCP. A PreToolUse hook on git commit automatically runs blast-radius analysis on staged changes — no manual steps needed.
<!-- gitnexus:end -->