# CLAUDE.md Quick-reference for AI assistants working in the Seamless repository. For coding conventions see `AGENTS.md`. ## What is Seamless? Seamless is a local-first agent memory and coordination substrate. Its clients are AI agents (Claude Code and friends); the human is an observer/editor. It provides persistent memory with a lifecycle (supersession, provenance, archival, a gardener), ambient sessions via Claude Code hooks, a dependency-aware task ready-queue, research trials, hybrid recall, and a read-mostly observability console. Go backend, no CGO, single binary + CLI. It is a ground-up rebuild of Seam v1 (a private predecessor, read-only reference). As of the P6 cutover (2026-07-10) Seamless is the sole agent-memory system, serving on port 8081 with data dir `~/.seamless` and env prefix `SEAMLESS_*`. v1 is decommissioned (launchd services disabled, port 8080 freed) with `~/.seam` preserved read-only as a fallback archive. ## Project structure ``` cmd/seamlessd/ server daemon: serve, doctor, export, import, install-hooks, map-repo, unmap-repo, retire-project, family, console-open cmd/seam/ headless CLI (agents + owner observability) [P2/P5] cmd/docsgen/ docs site generator: docs-src/ -> docs/docs/ (see SITE.md) cmd/demoseed/ thin CLI over internal/demokit: the console-fleet seed (branding screenshots) and `-scenes` (the shared fixture) cmd/seambench/ agent-scenario benchmark: `run` (headless scenario x condition matrix) + `report` (grade, uplift, version deltas). Driven by `make seambench`; see cmd/seambench/README.md docs-src/ docs site markdown + nav.yaml (source; docs/docs/ is output) internal/core/ domain types: Project, Memory, Session, Task, Trial, Event internal/config/ single YAML + env config (static key, budgets, briefing tunables, llm, peer_auth) internal/access/ peer_auth policy: who a request speaks for (the owner on TCP or the Unix socket, a restricted local account named by its kernel uid) and which projects it may reach (View); leases on locked projects, judged per request; Unix-socket peer credentials. Foundation layer internal/passkey/ WebAuthn passkey ceremonies over go-webauthn (approver passkeys for locked projects); stateless, imports nothing internal internal/approval/ approver credentials for locked projects: passkey and Touch ID enrollment over store/approvers.go (migration 032) internal/store/ SQLite: schema, FTS5, embeddings (BLOB + cosine), migrations; index_work.go mirrors the DB-native work record (tasks, trials, session findings) into the same fts table the files layer fills with memories and notes internal/events/ append-only event log; SSE fan-out; retrieval stats internal/validate/ path/title/name guards internal/agentproc/ names the agent process (claude/codex) that launched a seam process: the identity the session-start hook stamps and the MCP bridge sends, which binds a connection to its session internal/files/ markdown layer: frontmatter, atomic writes, watcher [P1] internal/markdown/ agent markdown -> sanitized console HTML (goldmark + bluemonday) internal/llm/ OpenAI (default), Ollama, Anthropic chat + embeddings [P1] internal/retrieve/ briefing assembler, prompt-context matcher, recall (RRF), console search [P2/P4] internal/lifecycle/ supersession, archival, provenance [P3] internal/screening/ write screening (optional feature, off by default): the screens, the hold policy, the held-writes queue the console releases or discards, the corpus review, drift quarantine, lockdown and access.refused; see AGENTS.md internal/feed/ write screening's metadata feed: event log -> metadata frames -> on-disk spool -> a configured Unix socket internal/commitfeed/ commit records' snapshot (the commits: option, off by default): sessions, tasks and the commits they recorded -> one JSON file an integrity monitor matches commits against # (the dependency-aware ready-queue + lease-based claiming live in # internal/store/tasks*.go -- there is no internal/tasks package) internal/gardener/ the propose-only passes (dedup, staleness, stale-stage, dead-weight, digest, stale-plan, memory-wanted -- the canonical list is RunOnce), plus the request- and split-driven proposals [P4] internal/plans/ captured CC plan vocabulary: tags, statuses, tracking task internal/mcp/ MCP tools (streamable HTTP, static bearer key or the peer_auth socket); see ToolCount [P2+] internal/hooks/ session hooks + CC plan-mode capture (PostToolUse etc.) [P2/P3] internal/console/ server-rendered observability UI (html/template + SSE) [P5] internal/capture/ SSRF-safe URL fetch [P4] internal/update/ update check + automatic updates: GitHub release list, install-kind detection, Target/Decide policy, attempt-record fold, /update/state.json, the Status every surface renders (briefing notice, console, doctor, /healthz). Never installs in-process: the daemon spawns a detached `seamlessd update --auto` (cmd/seamlessd/update_spawn*.go) to run the release's installer. Imports config + core only internal/releasesig/ Sigstore check that a release asset came from this repo's release workflow on its exact tag (pinned trusted root, offline): the updater's, and the macOS seam's for the Touch ID approver download (linked there from a darwin-only file) internal/archive/ instance archives: VACUUM INTO snapshot + corpus + manifest out; guarded tar extract and restore-or-merge back in. Never imports config internal/importer/ one-shot import of the v1 (~/.seam) snapshot internal/demokit/ importable fixture-seeding primitives + the demo spec sets; the shared seam under BOTH branding and the benchmark internal/bench/ agent-scenario benchmark: scenario table, condition arms, graders (repo assertions + event log + optional LLM judge), results/uplift aggregation, the on-disk artifact contract scripts/fixture/ the shared throwaway fixture: make-myapp.sh (demo repo) + harness.sh --mode record|bench (branding | benchmark arms) scripts/branding/ GH-Pages surface: record.sh, distill.py (verbatim-audited scene data), console-shots.js -- see its README.md macos/SeamApprove/ the optional macOS Touch ID approver app (Swift/SwiftPM, not part of the Go build): SeamApproveCore + the AppKit executable; scripts/approver/ builds (make approver) and notarizes it ``` Bracketed tags mark the phase that introduces the package; unbuilt ones do not exist yet. ## Common commands ```bash make build # ./bin/seamlessd + ./bin/seam (touches nothing live) make install # build + snapshot binaries/config to ~/.local/bin + # ~/.config/seamless, repoint the service and hooks, restart. # Refuses on a machine the curl installer set up (its service # file carries the installer's marker) unless FORCE=1 make test # unit tests make test-race # unit tests with the race detector make check # the full gate: build + vet + cross-check (darwin/windows # vet) + fmt-check + docs-check + installer-check + # site-check + lint + vulncheck + test-race make check-fast # the pre-commit subset (no build/vulncheck/test-race); # .githooks/pre-commit runs it make lint # golangci-lint make vet # go vet make fmt # gofmt tracked files -- NOT `gofmt -w .`, which rewrites other # agents' worktrees under dot-dirs that go's ./... skips make run # build + serve on 127.0.0.1:8081 make doctor # build + config/DB self-checks make console # open the console in a browser, pre-authenticated make clean # remove bin/ and coverage files make approver # macOS: the Touch ID approver app into bin/approver/ (ad-hoc # signed unless APPROVER_* is set; see scripts/approver/) make approver-test # its Swift unit tests (no keychain, no prompt) make docs # regenerate the docs site (docs-src/ -> docs/docs/, committed) make docs-check # fail if the committed docs site is stale (runs inside `check`) make docs-serve # regenerate + serve the site at 127.0.0.1:8899/docs/ make site-check # fail if the hand-written landing page (docs/index.html) drifts # from the installer or the CLI -- docs-check cannot see it make indexnow # submit the sitemap's URLs to the IndexNow engines (manual, # run after a deploy is live; DRY=1 previews the payload) make metrics # GitHub stars + release-asset download counts (needs gh) # single test go test ./internal/validate -run TestTitle -v # Two different things called "benchmark". Do not conflate them: make bench # Go hot-path MICRO-benchmarks (ns/op, offline, free) make seambench # the AGENT-SCENARIO benchmark: runs a real `claude` over the # seeded fixture in each condition arm (vanilla/mechanism/full), # grades it, and prints with-vs-without uplift + version deltas. # SPENDS REAL API TOKENS; needs a `claude` binary + credentials. # MANUAL ONLY -- deliberately not in check, check-fast, the # pre-commit hook, CI, the release flow, or any schedule. # N=5, BASELINE=, SCENARIO=, CONDITIONS=, OUT=, # RUN_ARGS=, REPORT_ARGS=--no-trials. See cmd/seambench/README.md ``` ## Tech stack | Layer | Choice | |---|---| | Language | Go 1.26+ (no CGO) | | Database | SQLite (`modernc.org/sqlite`, WAL, FTS5) | | Vectors | float32 BLOBs in SQLite + brute-force cosine (no ChromaDB) | | LLM/embeddings | OpenAI (default, first-class), Ollama, Anthropic | | IDs | ULID (`oklog/ulid/v2`), never UUID | | Auth | single static bearer key, localhost bind (no JWT); optional peer_auth Unix socket that names each caller by its kernel-reported uid | | Console | `html/template` + vanilla JS + SSE (no node/React) | | Tests | testify/require, table-driven, fresh/in-memory SQLite | ## Configuration Single YAML file (`$SEAMLESS_CONFIG`, `~/.config/seamless/seamless.yaml`, or `./seamless.yaml`; see `seamless.yaml.example` for every key) with `SEAMLESS_*` env overrides -- env wins over file, file over defaults. The `briefing:` block tunes what the SessionStart `` auto-injects (section counts, recency filters, family cross-over, hard-cap multiplier, and the utility-vs-recency index order via utility_mode/utility_weight); those knobs are also editable live in the console (Settings -> Briefing injection), which stores a runtime override in the DB that wins over file/env until reset and applies from the next session start without a daemon restart. `gardener.session_idle_minutes` is the single live/idle threshold shared by the session reaper, the console and automatic updates' idle gate. The `update:` block (check, check_interval, auto, max_defer, min_age) drives the update check and automatic updates; its console override (Settings -> Updates) merges the other way round: `check: false` or `auto: false` in the file/env is final and locks its toggle (false check means no update traffic at all). Durations are `config.Duration` ("6h"; a bare number other than 0 is refused). The `peer_auth:` block (off by default; see docs-src/guides/peer-auth.md) adds a Unix-socket listener whose callers are named by the kernel's peer uid, locks the projects it lists until the owner approves an agent's access, and lists restricted local accounts; a client of it sets `server_url: unix:///path.sock` and needs no key. The `commits:` block (off by default; docs-src/guides/security-monitor.md) offers session_end and tasks_update a `commits` argument (`record`) and writes the commit-matching snapshot to `snapshot_file`. ## Storage layout ``` ~/.seamless/ seam.db SQLite: indexes, sessions, tasks, trials, events, embeddings, ... seamlessd.lock serve's one-daemon-per-data-dir lock (holder's PID; never delete it) memory/{project|_global}/{name}.md one memory per file (source of truth) notes/{project|_global}/{slug}.md work artifacts (source of truth) backups/pre-update-v*.tar.gz the updater's pre-update archives (newest 2) update/state.json the update check's record (release daemons only) update/update.lock the updater's lock (never delete it) update/attempt.json, attempts.jsonl, logs/ the updater's records (it writes, the daemon reads) feed/ write screening's metadata-feed spool (only with screening.feed_socket set) ``` Files are the source of truth for durable knowledge; the DB is the record for high-churn state (sessions, tasks, trials, events, telemetry) and a rebuildable index for the files. Under `peer_auth` the data dir belongs to the daemon's own OS account instead (the guide's layout: `/opt/seamless/data`, 0700), and serve refuses to start when it, the socket's directory, the config file or the binary could be rewritten by another account. ## Memory frontmatter ```yaml --- id: 01K... # ULID kind: gotcha # constraint|convention|runbook|protocol|gotcha|decision|refuted|reference|stage name: chroma-boot-race description: one line, <=150 chars -- the ONLY text shown in indexes project: seam # empty = global created: 2026-07-10T18:00:00Z updated: 2026-07-10T18:00:00Z valid_from: 2026-07-10T18:00:00Z invalid_at: null # set on supersession/archive; invalid memories leave indexes superseded_by: null # ULID of the replacement source_session: 01K... # the writing session's ULID (not its cc/... name) model: claude-fable-5 # the model that produced the content, verbatim as the provider names it tags: [x, y] --- body markdown ``` ## MCP surface The authoritative count is `internal/mcp.ToolCount` (asserted by doctor and by `catalog_test`); `mcp.Catalog()` is the same list as data, and the docs site renders its reference from it. Do not transcribe a number here -- three different stale counts is exactly how this section drifted before. sessions (`session_start/update/end`), memory (`memory_write/append/read/delete`, write carries `supersedes`), discovery (`recall` -- the only search tool, RRF-fused, spanning durable knowledge AND the work record: memories, notes, tasks, trials, session findings), notes (`notes_create/read/update/append/delete`), projects (`project_list/create`), tasks (`tasks_add/update/ready/list` -- `tasks_list id=` loads a single task by its globally-unique id -- plus `tasks_claim`/`tasks_release` for atomic lease-based claiming), research lab (`lab_open`, `trial_record`, `trial_query`), gardener (`gardener_proposals`, `gardener_apply`, plus the natural-language `gardener_request` and the project-split planner `gardener_split` -- both LLM-backed, and both only ever propose), utility (`capture_url`, `usage_summary`, `favorite_set` -- star/unstar any entity; starred memories pin into briefings and boost recall). Project and session are inherited from the session binding; agents in mapped repos rarely pass `project` explicitly. A Claude Code or Codex agent's connection is bound to its own ambient session automatically (the agent-process identity, `internal/mcp/agent_binding.go`), so those agents never need `session_start`. ## Plans as composition A "plan" is not a primitive -- it is a composition keyed by `plan:`: - **Narrative + supporting knowledge**: one primary note, plus any further notes tagged `plan:`, so the next agent inherits the design and its supporting context. `notes_create` already accepts `tags`. - **Steps**: tasks created with `plan:` (`tasks_add plan=...`). Plan-step tasks are excluded from the default `tasks_ready`/`tasks_list` (no queue pollution) and surfaced with `plan=`. - **Claiming**: `tasks_claim` atomically moves a ready task to `in_progress` with a lease (default 900s); re-claiming refreshes the lease (heartbeat); an expired lease is reclaimable. `tasks_release` (or closing the task, or `session_end`) frees it. The briefing surfaces each active plan as a `PLAN: -- ...` line. Claude Code plan mode feeds this automatically (`internal/hooks` capture + `internal/plans` vocabulary): plan-file saves upsert a `cc-plan-` note (`plan-status:draft|presented|approved|abandoned|shipped` tag -- the last two are the gardener's stale-plan settlements: shipped when git evidence shows the work landed unapproved, abandoned otherwise), planning subagents cache as `cc-agent-` notes in the composition, approval creates the tracking task, and the briefing lists unapproved captures as `PLAN (awaiting approval)` lines. Owner surfaces: `/console/plans` and `seam plan list|show|check|approve` (check = git-stamp staleness; approve = escape hatch when CC skips the approval hook). ## One fixture, two workflows `scripts/fixture/harness.sh` stands up the shared throwaway fixture -- the `myapp` demo repo plus a seeded Seamless instance on a non-live port, never the live `~/.seamless`/`~/.claude`/`~/.codex`. Above that seam the two halves are independent and non-interfering: - **Branding** (`--mode record`): record interactive with/without takes, distill them **verbatim** into `docs/static/scenes.js`, publish. Entry point `scripts/branding/record.sh`; recipe in `scripts/branding/README.md`. - **Benchmark** (`--mode bench`): build the `vanilla`/`mechanism`/`full` condition arms, run scenarios **headless**, grade, report uplift. Entry point `make seambench`; recipe in `cmd/seambench/README.md`. The demo repo must contain zero Seamless vocabulary (memory `scene-demo-repo-must-be-seamless-free`), or the without/vanilla side reads its way out of character. Grading discipline for benchmark code is in `AGENTS.md`. ## Working here - Conventions live in `AGENTS.md`. Read it before writing code.