claw-branch 0.1.2

Fork, simulate, and merge engine for ClawDB agents.
Documentation

claw-branch

License: Apache-2.0 Docs Crates.io

claw-branch is the branch orchestration engine for ClawDB-style workflows.

It provides isolated, SQLite-backed branch execution for agent and application workloads where you need to fork a trusted baseline, run experiments safely, inspect exact diffs, and merge with explicit conflict policy.

Why this crate exists

Traditional branch workflows in application databases are hard to make deterministic, inspectable, and safe for concurrent agents. claw-branch solves this with:

  • File-level branch isolation (one SQLite DB file per branch).
  • Explicit lineage and merge-base tracking via DAG.
  • Three-way merge and selective commit primitives.
  • Sandboxed simulation with recommendation output.
  • Built-in metrics and divergence reporting.
  • Snapshot verification and garbage-collection tooling.

Installation

[dependencies]
claw-branch = "0.1.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }

Optional guarded mode (integrates with claw-guard policy checks):

[dependencies]
claw-branch = { version = "0.1.2", features = ["guarded"] }

Production recommendation: use GuardedBranchEngine for built-in session and policy checks. BranchEngine remains the bare engine for environments that already enforce auth externally.

Feature flags

Feature Default Description
guarded no Enables GuardedBranchEngine integration with claw-guard

Architecture overview

┌─────────────────────────────────────────────────────────────────┐
│                         BranchEngine                           │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────────┐    │
│   │ Lifecycle│  │  Commit  │  │  Merge   │  │   Sandbox   │    │
│   │ fork/    │  │ cherry-  │  │ 3-way +  │  │ run/eval    │    │
│   │ archive  │  │ pick/all │  │ resolver │  │ recommendation│   │
│   └────┬─────┘  └────┬─────┘  └────┬─────┘  └──────┬──────┘    │
│        │              │              │                │           │
│   ┌────▼──────────────▼──────────────▼────────────────▼──────┐   │
│   │                  BranchStore (registry DB)                │   │
│   └───────────────────────────────────────────────────────────┘   │
│                                                                   │
│   ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌─────────────┐      │
│   │   DAG    │  │   Diff   │  │ Metrics  │  │  Snapshot   │      │
│   │ lineage  │  │ entity + │  │ tracker/ │  │ copy/verify │      │
│   │ + LCA    │  │ field    │  │ reporter │  │ + gc        │      │
│   └──────────┘  └──────────┘  └──────────┘  └─────────────┘      │
└─────────────────────────────────────────────────────────────────┘

Each branch is a separate SQLite file.
The trunk is the canonical root branch and is never discarded.

Quick start

use claw_branch::prelude::*;
use std::path::PathBuf;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let config = BranchConfig::builder()
        .workspace_id(uuid::Uuid::new_v4())
        .branches_dir(PathBuf::from("/tmp/demo/branches"))
        .build()?;

    let engine = BranchEngine::new(config, std::path::Path::new("/data/source.db")).await?;
    let feature = engine.fork_trunk("feature/summariser").await?;

    let trunk = engine.trunk().await?;
    let diff = engine.diff(trunk.id, feature.id).await?;
    println!(
        "changed: +{} ~{} -{}",
        diff.stats.added, diff.stats.modified, diff.stats.removed
    );

    let report = engine
        .simulate(
            feature.id,
            SimulationScenario {
                name: "trial-run".into(),
                description: "evaluate summarisation strategy".into(),
                max_ops: Some(100),
                timeout_secs: Some(30),
                seed_data: None,
            },
            |pool| async move {
                let _ = pool;
                Ok(serde_json::json!({"status": "done", "summaries_generated": 42}))
            },
        )
        .await?;

    match report.recommendation {
        Recommendation::Commit => engine.commit_to_trunk(feature.id).await?,
        Recommendation::Discard => engine.discard(feature.id).await?,
        Recommendation::NeedsReview(notes) => {
            println!("manual review required: {notes:?}");
        }
    }

    Ok(())
}

Core concepts

Concept Description
Branch Isolated SQLite snapshot with lifecycle state
Trunk Canonical baseline branch
Fork Snapshot copy of a parent branch
Diff Entity-level and field-level comparison
Merge Three-way merge with strategy-based conflict handling
Commit Selective or full promotion to a target branch
Sandbox Temporary evaluation environment with recommendation output
DAG Branch lineage graph with cycle checks
Metrics Branch counters, size, and divergence signals
GC Snapshot cleanup for discarded/orphan branches

Public module map

Module Purpose
branch Branch model, naming, lifecycle, and persistence
commit Full and selective commit flows including cherry-pick
dag Lineage graph, traversal, merge-base, and serialization
diff Diff extraction, scoring, and formatting
merge Three-way merge, strategies, and conflict resolution
metrics Divergence tracking and workspace reporting
sandbox Simulation environment and evaluation routines
snapshot Snapshot copy, integrity sidecar, and cleanup

BranchConfig reference

Field Default Description
workspace_id required Unique workspace UUID
branches_dir required in builder Directory containing branch SQLite files
registry_db_path branches_dir/branch_registry.db Registry SQLite path
trunk_branch_name trunk Canonical branch name
divergence_threshold 0.50 Threshold used by sandbox recommendation
max_branches_per_workspace 100 Hard cap on active branches
gc_orphan_threshold_secs 86400 Orphan age cutoff for GC
auto_metrics true Auto refresh metrics on lifecycle operations

Merge strategies

Strategy Behavior
MergeStrategy::Ours Prefer source values on conflict
MergeStrategy::Theirs Prefer target values on conflict
MergeStrategy::Union Union JSON-like structures where possible
MergeStrategy::FieldLevel(map) Per-field strategy overrides
MergeStrategy::Manual Preserve conflicts for explicit review

Simulation lifecycle

  1. Fork a temporary simulation branch from a parent.
  2. Run agent logic against the simulation SQLite pool.
  3. Compute diff and metrics.
  4. Produce Recommendation::Commit, Recommendation::Discard, or Recommendation::NeedsReview.
  5. Apply workflow policy (promote/discard/review).

Error model

Most operations return Result<T, claw_branch::error::BranchError> and are designed to fail explicitly on:

  • Invalid branch names and lifecycle transitions.
  • Snapshot or sidecar integrity violations.
  • DAG cycle or invalid ancestry operations.
  • SQLite IO and migration failures.
  • Merge conflicts requiring manual strategy.

Safety and invariants

  • No unwrap/expect in library source (clippy::unwrap_used denied).
  • Public API docs required (missing_docs denied).
  • SQLite isolation by branch file to avoid cross-branch mutable overlap.
  • DAG cycle prevention before edge insertion.
  • Atomic merge and commit transactions.
  • Snapshot integrity checks via BLAKE3 hashes.

Performance targets (design goals)

These targets are validated by benchmark suites in benches/branch_bench.rs.

Operation Target
Fork 1k entities < 50ms
Diff 10k entities (10% modified) < 200ms
Merge 100 non-conflicting entities < 100ms
Snapshot verify (10MB) < 20ms

Compatibility

  • Rust edition: 2021
  • MSRV: 1.75 (see Cargo.toml)
  • Runtime: Tokio
  • Storage backend: SQLite via SQLx

Development and CI commands

cargo build --all-features
cargo test --all-features
cargo clippy --all-targets --all-features -- -D warnings
cargo fmt --all -- --check
cargo deny check
cargo audit
cargo bench --no-run
cargo bench -- --test

Release process

  1. Update Cargo.toml version and CHANGELOG.md.
  2. Ensure CI passes on main.
  3. Create and push a version tag (vX.Y.Z).
  4. publish.yml verifies the tag/version match, publishes to crates.io, and creates a GitHub release.