bones

module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 27, 2026 License: Apache-2.0

README

bones

State memory and real-time comms for embedded agent processes, built on Fossil (durable state) and NATS (live coordination). Beads-inspired — but without the git cleanup tax that multi-agent divergence imposes on branch-based VCS.


Quickstart

git clone https://github.com/danmestas/bones
cd bones
# Clone EdgeSync sibling repo (required for bin/leaf):
cd .. && git clone https://github.com/danmestas/EdgeSync && cd bones
# Build bones binaries:
make
# One-command bootstrap: workspace + scaffold + bin/leaf + hub:
bin/bones up
# Add and inspect tasks:
bin/bones tasks add "my first task" --files src/foo.go
bin/bones tasks open
bin/bones tasks status

Or use fine-grained control (bones up is equivalent to):

bin/bones init
bin/bones orchestrator
bash .orchestrator/scripts/hub-bootstrap.sh

See bin/bones --help for the full subcommand list (add, claim, close, watch, status, and more).


Why this project exists

Multi-agent software development — where 5–10 Claude subagents collaborate on a single codebase in parallel — needs two things that are hard to get from today's tooling:

  1. Durable, multi-writer state. Each agent is a short-lived process that may crash or be restarted. Their work needs to persist, be visible to peers, and converge deterministically without a human unsnarling merge conflicts.
  2. Real-time coordination. Agents need to announce what they're doing, negotiate collisions, ask each other questions, and report status — with low latency and without coupling to a specific orchestrator implementation.

Beads (https://github.com/gastownhall/beads) solves the durable-state half of this problem by putting task graphs into Dolt — a version-controlled SQL database with cell-level merge. Dolt's merge model is genuinely strong for structured state, and beads' dependency-graph + hash-ID approach (no collisions from independent writers) is well-suited to multi-agent task tracking. Beads also ships built-in messaging (an issue type with threading), graph links (relates_to, duplicates, supersedes, replies_to), and semantic "compaction" that summarizes old closed tasks to conserve agent context window.

What beads leaves open: code artifacts and real-time comms. Beads users still manage their actual code in git (with the cleanup tax that implies at many-agent scale), and beads' messaging is durable-state-only — it's a memory, not a message bus. No live presence, no subscribe-on-change push, no ephemeral coordination layer.

Fossil — specifically the embeddable libfossil API we already use in EdgeSync — offers a fundamentally different posture:

  • Content-addressed DAG, not branches-with-refs. Divergence is a first-class concept tolerated indefinitely.
  • Autosync converges lazily through a shared repo DB; agents don't orchestrate push/pull themselves.
  • Fossil's fork model means two agents can commit in parallel with zero cleanup — the next commit referencing both leaves is the merge.
  • Identical blobs deduplicate automatically across agents editing similar files.

NATS provides the ephemeral, low-latency side: hold announcements, chat, request/reply, TTL-based presence state.

The bones thesis. Fossil (code + structured state) plus NATS (live coordination) gives a single substrate where code and tasks converge together, and where agents can do real-time negotiation without an external broker. That's the unification beads doesn't offer: beads users keep code in git and have no built-in live-comms layer. We get beads-style durable memory for tasks, plus the code-artifact story beads doesn't try to solve, plus the message-bus layer beads doesn't include.

Honest acknowledgment of where beads is stronger today: Dolt's cell-level merge is purpose-built for structured state in a way file-based task storage isn't (we'll handle conflicts at the application layer on JSON/markdown files). Beads' compaction (semantic summarization of old closed tasks) is genuinely useful for long-horizon agent memory — we'll likely port an equivalent. These land on the open-questions and roadmap lists, not as concessions but as clarity about what we're building and what we're deferring.


Goals

  • Embed-ability. A Go library agents link into their own process, plus a small CLI surface for humans.
  • Infrastructure only. We provide primitives. Orchestration policy (who steers whom, when to spawn, how to delegate) lives in a separate consumer codebase.
  • Persistence across crashes. The fossil repo is the memory; agents come and go.
  • Coordination without hard locks. Optimistic announcements plus chat-based conflict resolution. No distributed locking, no deadlocks.
  • Beads capability parity. Cover beads' core feature set with fossil-native equivalents; document any deliberate non-coverage.
  • Orchestrator-agnostic. We don't prescribe an orchestration shape. Consumers layer policy on top.

Non-goals

  • Orchestrator intelligence. No built-in "how should the work split" logic. That belongs upstream to consumers.
  • Replacing EdgeSync or libfossil. We import them; we don't fork their responsibilities.
  • NATS extensions. We consume NATS as-is. No upstream PRs to nats-server or nats.go.
  • Workflow DSLs. No YAML task graphs, no declarative agent definitions. Consumers define those if they want them.
  • Beads as a runtime dependency. Beads is reference material and audit target, not a library we link to.

Relationship to other projects

Project Role PR policy
libfossil (danmestas) Fossil primitives: Repo, Checkout, sync, timeline PR upstream when new primitives are needed (e.g., observer hooks, task artifact support)
EdgeSync (danmestas) Leaf daemon, embedded NATS, notify system PR when the substrate needs a change bones can't make locally
nats-server, nats.go Real-time transport Consume only — no upstream PRs from this project
beads (gastownhall) Reference design & audit target Cloned at reference/beads/; no runtime dependency

Dependency arrow: bones → EdgeSync → libfossil. Linear. No back-edges. If we notice EdgeSync reaching up into bones, that's a smell — push the primitive the other direction.


Architecture (first pass)

   Agent Process A                   Agent Process B
   ┌────────────────┐               ┌────────────────┐
   │  coord.Coord   │               │  coord.Coord   │
   │  ├ AnnounceHold│               │  ├ AnnounceHold│
   │  ├ ClaimTask   │               │  ├ ClaimTask   │
   │  ├ Post / Ask  │               │  ├ Post / Ask  │
   │  └ Subscribe   │               │  └ Subscribe   │
   └────────┬───────┘               └───────┬────────┘
            │                               │
            ├──── NATS ─────────────────────┤
            │   (holds KV, chat, req/reply) │
            │                               │
            └──── Fossil repo ──────────────┘
                  (tasks/ dir, code commits,
                   timeline is audit trail)

Each agent:

  • Holds one coord.Coord instance
  • Owns a checkout directory (checkouts/<agent-id>/) backed by the shared repo DB
  • Publishes file-hold announcements, chat messages, task-state updates
  • Subscribes to peer announcements and directive subjects
  • Commits work directly to the repo; autosync propagates via the leaf daemon

The shared repo holds:

  • Code — normal commits to content files
  • Tasks — markdown-or-JSON files under tasks/ (one per task; state in frontmatter)
  • Timeline — canonical audit trail for all of the above

NATS carries:

  • <proj>.holds.current — KV bucket with TTL, keyed by file path, value = {agent_id, claimed_at, checkout_path}
  • <proj>.holds.announce / .release — pub/sub events when holds start/end
  • <proj>.chat.<thread> — durable-ish chat threads (maps onto EdgeSync notify subjects)
  • <proj>.coord.<thread> — short-lived negotiation threads (collision resolution, task handoff)
  • <proj>.orch.<agent-id>.cmd / .report — orchestrator ↔ agent channels (naming convention only; no enforced semantics)

Initial plan

Phase 0 — scaffolding
  • Directory layout created at /Users/dmestas/projects/bones
  • This README drafted
  • Clone reference/beads for audit
  • Read beads' agent config first — AGENTS.md, AGENT_INSTRUCTIONS.md, CLAUDE.md, claude-plugin/ — since how beads presents itself to coding agents is the primary pattern we want to borrow
  • Read beads' internal/ packages and Dolt-backed data schema to understand its storage model; draft reference/CAPABILITIES.md (beads feature → our equivalent, with gaps flagged)
  • go.work.example pointing at ../EdgeSync and ../libfossil
Phase 1 — hold protocol
  • internal/holds/ — NATS KV bucket with TTL; Announce, Release, WhoHas, Subscribe helpers
  • Unit tests against an embedded nats-server (pattern borrowed from EdgeSync/leaf/agent/nats_mesh.go)
  • Decision: whether the holds package is internal or part of the public coord API
Phase 2 — tasks-as-files
  • Task schema: markdown file with YAML frontmatter (id, status, claimed_by, parent, context)
  • internal/tasks/Claim, Update, List, Watch on top of r.Commit() and r.Timeline()
  • Define the conflict model: if two agents claim the same task, fossil creates a fork — first-wins or chat-resolved (design decision deferred to this phase, not earlier)
Phase 3 — Coordinator API
  • coord/coord.goCoord struct composing holds + tasks + chat
  • Chat = direct reuse of EdgeSync's notify service
  • Explicit Ask(ctx, recipient, question) → (answer, err) using NATS request/reply
Phase 4 — auto-init + CLI
  • cmd/bones/ — walk up to find .agent-infra/ marker or create one at the invocation directory; start (or join) a local leaf daemon; human-facing tasks subcommands (list, claim, update, close)
  • Decide: explicit bones init command vs silent walk-up
Phase 5 — smoke + chaos tests
  • examples/two-agents/ — harness that spawns two subagents via Claude Code Task tool, each gets a checkout, writes complementary files, syncs, chats
  • examples/two-agents-commit/ — sibling harness exercising the Phase 5 code-artifact surface end to end: coord.Commit, OpenFile, Checkout, Diff, fork-on-conflict, and Merge per ADR 0010
  • Chaos: overlapping edits, simultaneous task claim, agent kill-and-restart mid-commit
Phase 6 — beads capability closure
  • Walk through every item in CAPABILITIES.md; either implement or explicitly mark as non-goal with rationale
  • Write a short "migrating from beads" note if the overlap warrants it
Phase 7 — MCP integration (per hf1)
  • ADR pinning whether coord exposes an MCP server directly or whether a separate cmd/agent-mcp/ wraps it
  • Lets external LLM agents consume coord primitives via the Model Context Protocol
Phase 8 — ACL / role-based authorization (per ba6)
  • ADR pinning the ACL model (per-task ACL, per-project role, JWT-carried claims)
  • Gates AskAdmin, Merge, Reclaim, and other surfaces currently open to any agent

Repository layout (planned — created as phases land)

bones/
  coord/              # Public Go API — the surface agents import
  cmd/
    bones/            # Unified CLI: init/up/orchestrator + tasks subcommands
  internal/
    holds/            # NATS KV hold protocol
    tasks/            # tasks-as-files helpers
  reference/          # Read-only study snapshots — not where code is written
    beads/            # Audit target (gastownhall/beads, Dolt-backed)
    EdgeSync/         # Local clone of ../EdgeSync for source reading
    go-libfossil/     # Local clone of ../libfossil for source reading (historical path name preserved)
    nats-server/      # Shallow clone (nats-io/nats-server)
    nats.go/          # Shallow clone (nats-io/nats.go)
    CAPABILITIES.md   # Side-by-side: beads feature → our equivalent (TBD)
  examples/
    two-agents/         # Smoke-test harness (Phase 3+4 coord primitives)
    two-agents-commit/  # Smoke-test harness (Phase 5 code artifacts per ADR 0010)
  go.work.example     # Points at ../EdgeSync and ../libfossil (NOT reference/)
  GETTING_STARTED.md  # Fresh-session handoff doc
  README.md           # This file

Development setup

This repo carries reference clones of every project we study or depend on under reference/ (see §Repository layout). Those are read-only study snapshots — use them for mgrep and source reading, not development.

Active development of EdgeSync or libfossil happens at the canonical sibling paths:

  • /Users/dmestas/projects/EdgeSync
  • /Users/dmestas/projects/libfossil

Local workspace — copy go.work.example to go.work so edits to canonical siblings propagate without a publish cycle:

cp go.work.example go.work
go build ./...
go test ./...

go.work.example points at the canonical sibling paths, not at reference/ — the reference clones are not where code gets written. If you find yourself editing inside reference/ that's a sign something's miswired.

Getting started with bones

bones init creates an .agent-infra/ workspace and starts a local leaf daemon, or rejoins an existing workspace from any subdir.

# Build the binary (leaves it at ./bin/bones):
make bones

# First time in a fresh directory — starts a leaf, writes .agent-infra/:
$ ./bin/bones init
workspace=/path/to/dir
agent_id=7c3d…
nats_url=nats://127.0.0.1:4222
leaf_http_url=http://127.0.0.1:51234

# From any descendant directory — walks up to find the marker:
$ ./bin/bones join
workspace=/path/to/dir
…

The leaf binary must be on PATH (or LEAF_BIN set to its absolute path). Build it from EdgeSync with cd ../EdgeSync && make leaf. Leaf stdout/stderr lands in .agent-infra/leaf.log; the PID sits at .agent-infra/leaf.pid. Set AGENT_INFRA_LOG=json to switch bones's own logs to JSON.

Release-time: each of the three repos (bones, EdgeSync, libfossil) is tagged and published independently. bones consumes tagged versions of the other two. No monorepo pressure.

Guardrail: a pre-commit or lint check (to be added in Phase 1) that fails if this repo imports from internal/ paths of EdgeSync or libfossil. Keeps the boundary honest when we're moving fast and tempted to "just reach in."

Future git init note: when this project is eventually git init'd, the reference/ subtrees should go in .gitignore — we don't want nested git repos tracked as ghost submodules.


Configuration

All environment variables consumed by bones binaries and scripts are documented in docs/configuration.md, including defaults and which binary reads each variable.

Quick reference of the most common knobs:

Variable Default Purpose
AGENT_INFRA_LOG text Set to json for JSON log output
LEAF_BIN (resolved) Absolute path to the leaf binary
EDGESYNC_DIR ../EdgeSync Path to EdgeSync sibling repo
OTEL_EXPORTER_OTLP_ENDPOINT (disabled) OTLP collector endpoint
HERD_AGENTS 16 Agent count for herd trial harness

Open questions (resolve as we go)

  1. Naming. See working-title note at the top.
  2. Task storage. Starting with files-in-repo. Graduate to custom xfer cards if commit noise becomes a problem. Third option: port fossil's ticketing system to libfossil — biggest lift, most native.
  3. Public API surface. Is coord/ the only exported package, or do we expose holds/ and tasks/ directly? Lean toward one narrow public entry point. Resolved (2026-04-18): coord/ is the sole exported package. See ADR 0001.
  4. Conflict resolution model. When two agents commit overlapping task state, what happens? Options: (a) last-writer-wins + notification, (b) fossil fork + chat-resolved merge commit, (c) NATS-KV-based pessimistic claim before commit. Leaning (b) — it uses fossil's native posture. Resolved (2026-04-18): Chose (b) fossil fork + chat notify. See ADR 0004.
  5. Auto-init UX. Walk-up discovery (like git) vs explicit init command. Walk-up is friendlier but does filesystem work on every CLI invocation.
  6. Orchestrator channel naming. Do we prescribe orch.<agent-id>.cmd / .report, or leave all subject naming to consumers? Probably prescribe (to enable generic tooling) but mark as convention, not protocol.
  7. Beads migration. Write a porting guide alongside v0.1, or defer until someone asks?

Status log

  • 2026-04-18 — Project kicked off. Directory created, README drafted, reference/beads cloned. Key finding during clone: beads is Dolt-backed (version-controlled SQL database with cell-level merge), not git-backed as the earlier conversational framing had assumed. Thesis updated above — we don't differentiate by "avoiding git" but by unifying code + state + comms in one substrate. Next: read beads' agent config files (AGENTS.md, CLAUDE.md, claude-plugin/) first since that's the primary pattern to study; then the internal/ packages for the data model; then draft CAPABILITIES.md.

Directories

Path Synopsis
Package cli holds embeddable Kong commands for the bones binary.
Package cli holds embeddable Kong commands for the bones binary.
cmd
bones command
Command bones is the unified bones CLI.
Command bones is the unified bones CLI.
space-invaders-orchestrate command
Command space-invaders-orchestrate is the orchestrator side of the Phase 3 behavioral test.
Command space-invaders-orchestrate is the orchestrator side of the Phase 3 behavioral test.
Package coord is the single public entry point for bones.
Package coord is the single public entry point for bones.
examples
herd-hub-leaf command
Package herdhubleaf is a thundering-herd trial harness for the hub-and-leaf architecture (ADR 0018, Phase 2 of hub-leaf-orchestrator).
Package herdhubleaf is a thundering-herd trial harness for the hub-and-leaf architecture (ADR 0018, Phase 2 of hub-leaf-orchestrator).
hub-leaf-e2e
Package hubleafe2e is an E2E sanity harness for the hub-leaf architecture (ADR 0018).
Package hubleafe2e is an E2E sanity harness for the hub-leaf architecture (ADR 0018).
two-agents command
Command two-agents is a smoke harness that spawns two child processes, each opening its own coord.Coord against a shared leaf, and asserts six Phase 3+4 coord primitives work across real process boundaries.
Command two-agents is a smoke harness that spawns two child processes, each opening its own coord.Coord against a shared leaf, and asserts six Phase 3+4 coord primitives work across real process boundaries.
internal
assert
Package assert provides runtime invariant checks for coord internals.
Package assert provides runtime invariant checks for coord internals.
chat
Package chat is the substrate layer that backs coord's Post, Ask, and Subscribe on top of EdgeSync's notify service.
Package chat is the substrate layer that backs coord's Post, Ask, and Subscribe on top of EdgeSync's notify service.
holds
Package holds is the substrate layer that stores file-level holds in a NATS JetStream KV bucket.
Package holds is the substrate layer that stores file-level holds in a NATS JetStream KV bucket.
jskv
Package jskv holds JetStream KV primitives shared across the CAS-backed substrate packages (internal/holds, internal/tasks, and any future Phase 4 consumer — presence, subscriber registry).
Package jskv holds JetStream KV primitives shared across the CAS-backed substrate packages (internal/holds, internal/tasks, and any future Phase 4 consumer — presence, subscriber registry).
presence
Package presence is the substrate layer that backs coord's Who and WatchPresence.
Package presence is the substrate layer that backs coord's Who and WatchPresence.
tasks
Package tasks is the substrate layer that stores task records in a NATS JetStream KV bucket.
Package tasks is the substrate layer that stores task records in a NATS JetStream KV bucket.
testutil/natstest
Package natstest provides an embedded NATS server fixture for tests.
Package natstest provides an embedded NATS server fixture for tests.
workspace
Package workspace manages a bones workspace: the .agent-infra/ directory, its on-disk config, and the associated leaf daemon process.
Package workspace manages a bones workspace: the .agent-infra/ directory, its on-disk config, and the associated leaf daemon process.

Jump to

Keyboard shortcuts

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