Architecture28 min read

Your Agent's Brain Deserves a Git Repository: Version-Controlled, Self-Hosted Agent Memory Over Tailscale

Why the right architecture for agent memory is a Git-backed OKF vault running on your own infrastructure, synced across machines via Tailscale, backed up to R2 or Arweave — and how ClawQL ships this as a single command.

Why the right architecture for agent memory is a Git-backed OKF vault running on your own infrastructure, synced across machines via Tailscale, backed up to R2 or Arweave — and how ClawQL ships this as a single command.

This pairs with The Complete Agent Memory Stack (the five-layer architecture: OKF vault, vector recall, PageIndex, CodeGraph, Onyx) and Immutable Releases (content-addressed artifact permanence). The .cqk format is defined in ADR 0010. Memory serialization: docs/memory/okf.md.


Why This Matters Now

Teams building serious agentic systems have been independently arriving at the same conclusion for months: object storage alone is the wrong backend for agent memory. The pattern keeps emerging because the problem is structural — agents that can’t roll back a bad decision, can’t review knowledge before it becomes canonical, and can’t trace exactly what their swarm knew at any given point in time aren’t production-ready agents. They’re demos.

ClawQL has been building toward this architecture since the beginning. The memory vault, the OKF format, the team sync model, the WORM audit trail — these weren’t designed in isolation. They were designed as an integrated system where git’s history and merge semantics are the missing piece that turns object storage into a governed knowledge base. This post documents that piece, how it fits into the rest of the stack, and why it matters that ClawQL ships it as a built-in capability of the Virtual Gateway rather than a separate service you operate.


Why Git Is the Right Storage Model for Agent Memory

Before getting into implementation, it’s worth being precise about what Git gives you that object storage (S3/R2 alone) doesn’t, and what object storage gives you that Git alone doesn’t. You need both, in the right roles.

What Git gives you:

Complete, auditable history with zero additional tooling. Every memory_ingest becomes a commit. git log shows the complete history of what agents learned, when, from which session. git diff shows exactly what changed between two knowledge entries. git blame shows which agent wrote which line. git bisect finds the commit where a wrong assumption entered the vault. None of this requires a special query language, a database, or vendor access.

Rollback that’s structural, not bolted on. If an agent ingests a wrong decision — it misunderstood a requirement, it hallucinated a constraint — git revert removes it from the vault without touching surrounding entries. The commit history preserves the record that it existed and was removed. This is the audit trail that compliance teams want: not just “what does the vault contain now” but “what did it contain on March 1, and who removed what and when.”

Review workflows before knowledge enters production. The PR model means that high-stakes knowledge — architectural decisions, security policies, organizational standards — can require human review before merging. An agent proposes a new decision. A developer reviews it. The knowledge doesn’t enter the shared vault until approved.

Merge semantics for concurrent writes. When two agents on two different machines ingest knowledge simultaneously, Git’s merge machinery handles this correctly the vast majority of the time. When conflicts do occur, they surface explicitly rather than silently overwriting.

Free delta compression. Markdown files with YAML frontmatter compress extremely well with git’s delta compression. A vault with 10,000 entries takes far less disk space than 10,000 individual files in flat object storage, because git stores differences rather than copies.

What Git alone doesn’t give you:

Durability at scale. A self-hosted Git repository is not durable storage by itself. Drives fail. For a team’s collective organizational memory, the repository needs to be backed up to durable remote storage — R2, S3, or Arweave for long-term permanence.

Semantic search. Git has no concept of embedding similarity. A query for “what did we try when the token validation was timing out?” doesn’t map to any git operation. That’s what the vector recall layer handles.

Cross-organization knowledge federation. Git repositories are isolated. When you want to query across personal, team, and org vaults simultaneously — with different authorization scopes for each — you need the namespace model that the five-layer memory stack provides on top of the git backend.

The correct model:

Git is the backend for the OKF vault. It provides history, rollback, review, and merge semantics. R2 or Arweave is the durability layer. The five-layer memory stack is the query layer. ClawQL provides all three — the git backend, the durability sync, and the recall stack — as an integrated system.

Lesson: git gives you history, rollback, review, and merge. Object storage gives you durability. Semantic search gives you recall. All three are necessary. None replaces the others.


The Architecture

ClawQL supports two deployment modes for the git remote. The vault format, the OKF entries, the five-layer recall stack, and the WORM audit trail are identical in both. The only difference is where the canonical bare repository lives.

Mode A: GitHub remote (zero extra infra)

Developer Laptop (Edge Gateway)
  ~/.ClawQL/vault/              ← git working copy
    .git/
      remote: origin → github.com/org/agent-memory (private repo)
    index.md
    decisions/
      auth-jwt-over-sessions.cqk
    ...

  ↓ git push (async, on every ingest)

GitHub (private repo)
  org/agent-memory              ← canonical bare repo, GitHub manages it
  GitHub Actions                ← post-push R2 backup on every push to main

ClawQL Virtual Gateway
  No git server                 ← VG is a consumer, not a host
  Webhook handler               ← receives GitHub push events, pulls + syncs
  NATS JetStream                ← notifies swarm after pull
  WORM sink                     ← per-tenant audit trail

Mode B: Self-hosted (thin HTTP backend in VG)

Developer Laptop (Edge Gateway)
  ~/.ClawQL/vault/              ← git working copy
    .git/
      remote: origin → clawql-vg.tailnet-name.ts.net (Tailscale)
    ...

  ↓ git push (async, via Tailscale)

ClawQL Virtual Gateway
  Thin git HTTP backend         ← canonical bare repo, VG hosts it
    /vault/org/agent-memory.git ← bare repo, no working tree
    /vault/hooks/post-receive   ← two responsibilities: R2 backup + NATS publish
  NATS JetStream
  WORM sink

  ↓ post-receive hook

R2 / S3 (durable backup)
  s3://org-vault-backup/        ← git bundle, updated after every push

Choosing between them:

Mode A: GitHub remoteMode B: Self-hosted
Extra infra to operateNoneThin HTTP backend in VG
AuthGitHub App or fine-grained PATSPIFFE SVID (existing fabric)
PR review UIGitHub nativeCommand Deck
R2 backup triggerGitHub Actions workflowpost-receive hook
VG notified of pushGitHub webhook → VG pullPost-receive NATS publish
Air-gapped deploymentsNoYes
CostGitHub plan costNegligible (VG already running)

Most companies using GitHub already should use Mode A — it removes the git server concern entirely and uses infrastructure they already pay for and trust. Mode B is for organizations with air-gap requirements, strict data sovereignty constraints that preclude GitHub, or environments where the Tailscale mesh is the only permitted network path.

In both modes:

Local working copy — the agent’s active vault. Sub-millisecond reads for recall. Hot-tier cache. Writes happen here first. Offline commits accumulate locally and push when connectivity returns.

R2/Arweave — durability and permanence. GitHub’s reliability is high but not permanent. R2 bundles are the recoverable backup. Arweave snapshots are the long-term archive for knowledge that needs to survive decades.


The Git Remote: GitHub or Self-Hosted

The canonical bare repository can live in two places. The implementation details differ. The vault behavior is identical.

Mode A: GitHub as the Remote

For companies already on GitHub, this is the right default. No git server to operate. No post-receive hooks to maintain. No additional infrastructure beyond what you already pay for.

Setup:

# Create a private GitHub repository: github.com/org/agent-memory
# Create a GitHub App or fine-grained PAT scoped to this repo only
# (write:contents on agent-memory — nothing else)

export CLAWQL_MEMORY_BACKEND=git
export CLAWQL_MEMORY_GIT_REMOTE=https://github.com/org/agent-memory.git
export CLAWQL_MEMORY_GIT_AUTH=github_app    # or github_pat
export CLAWQL_MEMORY_GIT_COMMIT_ON=ingest
export CLAWQL_MEMORY_GIT_PUSH_MODE=async

clawql memory clone

R2 backup via GitHub Actions:

# .github/workflows/vault-backup.yml
name: Vault R2 Backup
on:
  push:
    branches: [main]

jobs:
  backup:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # full history for complete bundle

      - name: Bundle and upload to R2
        run: |
          TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ)
          git bundle create vault-${TIMESTAMP}.bundle --all
          rclone copy vault-${TIMESTAMP}.bundle \
            r2:${R2_BUCKET}/git-vault/bundles/ \
            --s3-endpoint https://${CF_ACCOUNT_ID}.r2.cloudflarestorage.com
        env:
          RCLONE_CONFIG_R2_TYPE: s3
          RCLONE_CONFIG_R2_PROVIDER: Cloudflare
          RCLONE_CONFIG_R2_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
          RCLONE_CONFIG_R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
          CF_ACCOUNT_ID: ${{ secrets.CF_ACCOUNT_ID }}
          R2_BUCKET: ${{ secrets.R2_BUCKET }}

VG webhook handler — replaces post-receive hook:

The VG doesn’t host the repo. Instead it registers a GitHub webhook and pulls when notified:

// VG receives push events from GitHub
app.post('/webhooks/github-vault', async (req, res) => {
  // Verify GitHub webhook signature — reject anything unsigned
  const sig = req.headers['x-hub-signature-256'] as string;
  if (!verifyGithubSignature(req.rawBody, sig, CLAWQL_GITHUB_WEBHOOK_SECRET)) {
    return res.status(403).end();
  }

  res.status(200).end(); // acknowledge immediately — don't block GitHub

  // Pull new commits from GitHub
  await git.pull('--rebase', 'origin', 'main');

  // WORM entry for the sync
  await worm.write({
    event_kind: 'VAULT_SYNC_PULL',
    payload: { source: 'github_webhook', timestamp: new Date().toISOString() },
  });

  // Notify swarm — edge nodes that need to sync will pull
  await nats.publish('clawql.memory.vault.push', {
    tenant: CLAWQL_TENANT_ID,
    timestamp: new Date().toISOString(),
    source: 'github',
  });
});

PR review via GitHub native UI:

When memory_ingest detects a high-stakes entry, it pushes to a review branch and opens a PR via the GitHub API. Developers review in GitHub’s familiar PR interface. On merge, the webhook fires, the VG pulls, the swarm syncs. The Command Deck shows pending reviews by querying the GitHub API for open PRs on the vault repo — it surfaces them in the same review queue as self-hosted mode reviews, without needing to host the review UI.

// High-stakes ingest — GitHub mode
if (entry.requires_review) {
  const branch = `memory/review/${entry.type}/${slugify(entry.title)}`;
  await git.checkout('-b', branch);
  await git.commit(filePath, commitMessage(entry));
  await git.push('origin', branch);

  // Open PR via GitHub API
  await github.pulls.create({
    owner: GITHUB_ORG,
    repo: 'agent-memory',
    title: `[memory] ${entry.type}: ${entry.title}`,
    body: prBody(entry),
    head: branch,
    base: 'main',
  });
}

Branch protection on GitHub:

Repository settings → Branches → Add rule → main
  ✓ Require pull request reviews before merging (1 required reviewer)
  ✓ Require status checks (optional: clawql-memory-lint CI check)

Auto-approve paths (via GitHub Actions on PR open):
  - personal/**
  - context/**
Require review paths:
  - decisions/**
  - org/**

Mode B: Self-Hosted (Thin HTTP Backend in VG)

For organizations with air-gap requirements or strict data sovereignty constraints that preclude GitHub. ClawQL doesn’t bundle a full forge — no issues, no package registry, no CI runner. Just the minimum needed to make git push/pull work.

What you actually need from “Git as the versioning plane” is narrow:

  1. A git protocol endpoint (receive-pack / upload-pack over HTTP smart protocol)
  2. Post-receive hooks with exactly two responsibilities: R2 backup and NATS publish
  3. Authentication via existing SPIFFE SVIDs — no new user database

That’s a thin TypeScript wrapper around git http-backend (part of standard git) plus a focused post-receive hook. No database. No forge UI duplicating what the Command Deck already provides. No upgrade cycle beyond git itself.

Why the self-hosted mode doesn’t bundle a full forge either:

ClawQL already owns every interaction surface that matters:

  • Agents write and read through memory_ingest / memory_recall and the MCP tool surface
  • Humans govern through the Command Deck — review, approval, trust-tier decisions, ontology binding
  • Identity and transport are solved by the Zero-Trust Agentic Fabric — SPIFFE SVIDs, mTLS, Tailscale

A forge UI duplicates the Command Deck for the interactions that matter and adds maintenance surface, an upgrade cycle, and GPL licensing considerations for everything that doesn’t. The “no separate server to operate” claim stays true because the thin HTTP backend lives inside the VG process or as a tightly coupled sidecar in the same deployable unit — not a separately managed service with its own database and lifecycle.

The implementation:

// packages/clawql-vault/src/server.ts
// Thin wrapper around git http-backend — ~200 lines, no external forge dependency

import { spawn } from 'child_process';
import { IncomingMessage, ServerResponse } from 'http';
import { validateSpiffeSvid } from '../auth/spiffe';

export async function handleGitRequest(
  req: IncomingMessage,
  res: ServerResponse,
  vaultPath: string // path to bare repository
) {
  // Authenticate via SPIFFE SVID in client certificate
  // No username/password, no token database — uses existing fabric identity
  const svid = await validateSpiffeSvid(req);
  if (!svid.authorized) {
    res.writeHead(403);
    res.end('Unauthorized');
    return;
  }

  // Spawn git http-backend — part of standard git, handles all protocol details
  const gitBackend = spawn('git', ['http-backend'], {
    env: {
      ...process.env,
      GIT_PROJECT_ROOT: vaultPath,
      GIT_HTTP_EXPORT_ALL: '1',
      PATH_INFO: req.url,
      REQUEST_METHOD: req.method,
      CONTENT_TYPE: req.headers['content-type'] ?? '',
      QUERY_STRING: new URL(req.url!, 'http://x').search.slice(1),
    },
  });

  req.pipe(gitBackend.stdin);
  gitBackend.stdout.pipe(res);
}

The post-receive hook — two responsibilities, idempotent:

#!/bin/bash
# /vault/hooks/post-receive
# Runs after every successful push. Fast and focused.

REPO_PATH=$(pwd)  # bare repo path
TIMESTAMP=$(date -u +%Y%m%dT%H%M%SZ)

# Responsibility 1: durability upload to R2
# git bundle is self-contained — git clone bundle.bundle restores everything
BUNDLE=$(mktemp)
git bundle create "$BUNDLE" --all
rclone copy "$BUNDLE" "r2:${CLAWQL_R2_BUCKET}/git-vault/bundles/${TIMESTAMP}.bundle" \
  --s3-endpoint "https://${CLOUDFLARE_ACCOUNT_ID}.r2.cloudflarestorage.com" &
rm "$BUNDLE"

# Responsibility 2: NATS publish — notify swarm of new memory entries
# Index rebuilds are eventual and driven by this event, not by the hook itself
nats pub clawql.memory.vault.push \
  "{\"tenant\":\"${CLAWQL_TENANT_ID}\",\"timestamp\":\"${TIMESTAMP}\"}" \
  --server "nats://${CLAWQL_NATS_URL}" &

# Both operations are backgrounded — hook returns immediately
# Push response to client is never blocked by durability or notification
wait

The hook is intentionally minimal. Index rebuilds on edge nodes happen when they receive the NATS event and decide they need to pull — not synchronously in the hook. WORM entries for individual ingests are written by memory_ingest at commit time, not by the push hook. The hook’s job is exactly two things: durability and notification.

The bare repository:

The canonical remote on the VG is a bare repository (git init --bare). Bare repos have no working tree — they contain only the git object store and refs. This is the standard layout for any git server. Edge gateways maintain normal clones (working trees) of the bare repo. The bare repo never needs to be in sync with a working tree, which simplifies the hook logic and eliminates an entire class of state management problems.

# VG initialization — runs once on first deploy
git init --bare /vault/org/agent-memory.git
cp /vault/hooks/post-receive /vault/org/agent-memory.git/hooks/post-receive
chmod +x /vault/org/agent-memory.git/hooks/post-receive

PR review via the Command Deck:

High-stakes entries that require human review go through the Command Deck, not through a forge PR interface. When memory_ingest detects requires_review: true, it commits to a review branch rather than main and registers the pending review in the VG’s review queue. A developer opens the Command Deck, sees the proposed entry, reviews the agent’s reasoning, and approves or rejects. On approval, the Command Deck merges the branch to main via a standard git merge operation against the bare repo. The entry doesn’t appear in recall results until merged.

This keeps the interaction surface in one place — the Command Deck — rather than splitting governance between the Command Deck and a forge UI that duplicates it.

The optional Gitea sidecar:

For the minority of customers who want classic forge features — issues, PR review outside the Command Deck, external collaborator access, a browsable web interface — ClawQL provides an optional clawql-vault-gitea sidecar. Gitea (MIT licensed) is the right choice for this sidecar over alternatives because MIT is commercially clean for redistribution in customer VPCs without copyleft obligations. It is opt-in and not bundled by default. Enabling it doesn’t change the thin HTTP backend — Gitea’s repository browser points at the same bare repo the thin backend serves.

# Helm values — default installation
clawql-vault:
  enabled: true # thin HTTP backend always included
  gitea-sidecar:
    enabled: false # opt-in only

# For customers who need the full forge UI:
clawql-vault:
  gitea-sidecar:
    enabled: true
    image: gitea/gitea:latest
    # Points at the same bare repo — no data migration required

Lesson: the right question for any bundled dependency is “what interaction surface does this serve that we don’t already own?” A forge UI serves an interaction surface ClawQL already owns — the Command Deck. A thin git HTTP backend serves the git protocol, which ClawQL doesn’t need to reinvent. Bundle the minimum, own the surface that matters.


The Tailscale Integration

ClawQL already integrates with Tailscale for edge gateway mesh networking. The clawql-vault service is another endpoint on the same Tailscale network — no new infrastructure concept, just a new service on the same mesh.

# Edge gateway configuration
export CLAWQL_MEMORY_GIT_REMOTE=git+tailscale://clawql-vg.tailnet-name.ts.net/org/agent-memory.git
export CLAWQL_MEMORY_GIT_AUTH=spiffe        # uses existing edge gateway SPIFFE identity
export CLAWQL_MEMORY_GIT_PUSH_MODE=async    # don't block agent on push

The push goes to the VG over Tailscale’s encrypted WireGuard tunnel. The SPIFFE SVID — the same cryptographic identity the edge gateway uses for the sovereign handshake with the Regional Gateway — authenticates the git push. No separate SSH keys. No username/password. The identity the agent already has is the identity the vault uses.

Offline behavior:

Edge gateways working offline accumulate commits in the local working copy. When Tailscale connectivity is restored, the pending commits push automatically. The agent never loses ingested knowledge — local commits are durable on disk. The WORM entries for offline ingests are also queued locally and push when connectivity returns.


The OKF v0.2 Trust Signals

The previous memory stack post covered OKF v0.1 — the required type field, optional resource, description, tags, index.md catalog, log.md changelog. The Git-native memory architecture benefits from OKF v0.2, which adds trust signals that make the provenance of every entry machine-readable.

The new fields in OKF v0.2:

generated:
  by: agent-daniel-dev-01 # which agent produced this
  at: 2026-07-28T09:14:33Z
  tool: memory_ingest
  model: anthropic/claude-sonnet-4
  session: sess-9102

verified:
  by: human # human | evaluator | agent
  at: 2026-07-28T09:45:00Z
  method: pr-review # pr-review | evaluator | auto
  reviewer: [email protected]

sources:
  - url: https://company.atlassian.net/wiki/auth-policy
    fetched_at: 2026-07-28T09:12:00Z
  - session_id: sess-9102
    turn: 7

stale_after: 2026-10-28T00:00:00Z # 90 days from creation
status: current # current | stale | superseded | retracted
superseded_by: decisions/auth-oidc-migration.cqk

In the Git-native architecture, these fields become searchable commit metadata. A CI job that runs on a schedule can open PR review requests for entries past their stale_after date. A query for “show me all decisions that have never been human-verified” is a git log query filtered by verified.method. The trust signals turn the vault from a collection of Markdown files into a governed knowledge base with explicit lifecycle management.

The .cqk extension as tooling signal:

.cqk is ClawQL’s extension for knowledge entries that carry the full OKF frontmatter contract including ClawQL-specific fields (worm_ref, correlation_id, verdict). The extension tells clawql memory lint, the Command Deck, and CI pipelines to apply ClawQL-specific validation — required worm_ref, valid status enum, stale_after in the future. Plain .md files with the same frontmatter are fully OKF-compatible and work identically. .cqk is the promoted form with stricter tooling enforcement, not a hard format break.


Automatic Commits on Every memory_ingest

The core of the Git-native architecture is the auto-commit hook: every time memory_ingest writes a new entry, ClawQL immediately makes a git commit and pushes to the VG vault. No developer intervention. No batch sync job.

The implementation in clawql-memory:

// packages/clawql-memory/src/git-vault.ts

export const ingestToGitVault = (
  entry: KnowledgeEntry,
  options: GitVaultOptions
): Effect<void, GitVaultError, WORMService | GitService> =>
  Effect.gen(function* () {
    const git = yield* GitService;
    const worm = yield* WORMService;

    // 1. Write the .cqk file
    const filePath = yield* git.write(entryToPath(entry), entryToOkfMarkdown(entry));

    // 2. Update index.md and log.md
    yield* git.updateIndex(entry);
    yield* git.appendLog(entry);

    // 3. Stage and commit
    const commitHash = yield* git.commit(
      filePath,
      `feat(memory): ingest ${entry.type}/${entry.title}

type: ${entry.type}
agent: ${entry.agent_id}
session: ${entry.session_id}
confidence: ${entry.confidence_score}
worm_ref: ${entry.worm_ref}

Co-authored-by: ${entry.agent_id} <[email protected]>`
    );

    // 4. Push to VG vault (async — don't block the agent)
    yield* git.push('origin', 'main').pipe(
      Effect.fork,
      Effect.catchAll(() => Effect.void) // push failure never blocks ingest
    );

    // 5. WORM entry for the ingest
    yield* worm.write({
      event_kind: 'MEMORY_INGEST',
      payload: {
        entry_type: entry.type,
        entry_path: filePath,
        commit_hash: commitHash,
        agent_id: entry.agent_id,
        session_id: entry.session_id,
      },
    });
  });

The push is forked — it happens asynchronously after the commit. The agent’s response to the user is never blocked by a network operation. If the VG is temporarily unreachable, the commit is in the local repository and will push when connectivity is restored. The WORM entry is written synchronously — the audit trail is always complete even if the push fails.

The commit message format:

Every commit follows a structured format navigable with standard git tools:

feat(memory): ingest decision/Authentication: JWT over sessions

type: decision
agent: agent-daniel-dev-01
session: sess-8821
confidence: 0.94
worm_ref: sha256:a1b2c3d4...

Co-authored-by: agent-daniel-dev-01 <[email protected]>

git log --oneline reads like a human-readable summary of what the agent learned. git log --grep="type: decision" filters to architectural decisions. git log --author="agent-daniel-dev-01" filters to a specific agent’s contributions.

Session end entries:

At the end of every session, clawql-memory creates a type: context entry that captures the session’s final state:

---
type: context
title: "Session end: clawql-auth-refactor 2026-07-28"
description: "Authentication refactor — implemented JWT, pending argon2 integration"
status: current
generated:
  by: agent-daniel-dev-01
  at: 2026-07-28T17:45:00Z
  tool: session_end_hook
  session: sess-8821
stale_after: 2026-08-28T00:00:00Z
worm_ref: sha256:e5f6a7b8...
---

## Session Summary

Completed JWT token strategy. Key decisions made this session:
- [[decisions/auth-jwt-over-sessions]]
- [[decisions/auth-argon2-over-bcrypt]]

## Current State

- `parseConfig` refactored to support YAML ✓
- JWT validation middleware implemented ✓
- argon2 integration pending — blocked on benchmark results from CI

## Next Steps

1. Review argon2 benchmark results when CI completes
2. Wire JWT refresh token logic to Redis blocklist
3. Add rate limiting to /auth/token endpoint

The next session starts by recalling this entry. The agent knows exactly where work left off without any context-setting from the developer.


Conflict Resolution: Two Agents, One Vault

The happy path — more common than you’d think:

Knowledge entries live in directories named by type (decisions/, context/, errors/). Two agents working on different tasks ingest entries in different files. Git merges them without conflict. This handles the vast majority of concurrent writes.

Concurrent writes to shared catalog files:

index.md and log.md are touched by every ingest. ClawQL configures union merge for these files:

# .gitattributes (auto-generated by clawql memory init)
log.md merge=union      # union merge: keep all lines from both sides
index.md merge=union    # same

The union merge strategy concatenates both sides’ additions. A log.md with both machines’ entries is exactly what you want — a complete changelog. An index.md with all entries from both sides is exactly what you want — a complete catalog.

Conflicting entries on the same topic:

Two agents simultaneously ingesting entries with the same filename surfaces as a proper git merge conflict. This is the right behavior — two conflicting decisions about the same topic deserve human review. The Command Deck’s PR interface handles this review.

Rebase strategy for clean history:

clawql sync pull uses --rebase by default. Each commit is a discrete knowledge ingest event. The history reads chronologically even with concurrent writes from multiple machines. No merge commits cluttering the history.

Lesson: git’s merge machinery is the right conflict resolution system for knowledge vaults. Conflicts are explicit and reviewable. Silent overwrite — which cloud sync services do — is the wrong model for knowledge with provenance requirements.


Durability: R2 Backup and Arweave Permanence

Tier 2: R2 bundle backup

The VG’s clawql-vault service runs a post-receive hook after every push. The hook creates a git bundle — a single file containing the complete repository — and uploads to R2:

// Built into clawql-vault post-receive hook
const backupToR2 = async (repoPath: string) => {
  const timestamp = new Date().toISOString();
  const bundle = await git.bundle(repoPath, '--all');

  await r2.upload(bundle, {
    key: `git-vault/bundles/${timestamp}.bundle`,
    contentType: 'application/x-git-bundle',
  });

  // Latest snapshot for fast restore
  await r2.upload(bundle, {
    key: 'git-vault/latest.bundle',
    contentType: 'application/x-git-bundle',
  });

  // WORM entry for the backup
  await worm.write({
    event_kind: 'VAULT_BACKUP_R2',
    payload: { bundle_hash: sha256(bundle), timestamp },
  });
};

A git bundle is self-contained — git clone bundle.bundle produces a fully functional repository. The backup is independent of the VG infrastructure. If the VG host fails entirely, git clone r2://org-vault-backup/git-vault/latest.bundle restores the complete vault history.

Tier 3: Arweave permanence

For organizational knowledge that needs to survive indefinitely — architectural decisions from years past, compliance records regulators will ask for — periodic Arweave snapshots provide permanence that R2 alone doesn’t guarantee.

// Runs weekly via clawql-automation Argo Workflow
const archiveVaultToArweave = async () => {
  const bundle = await git.bundle('--all');
  const merkleRoot = sha256(bundle);

  const txId = await arweave.upload(bundle, {
    tags: [
      { name: 'Content-Type', value: 'application/x-git-bundle' },
      { name: 'ClawQL-Vault-Version', value: currentVersion },
      { name: 'ClawQL-Merkle-Root', value: merkleRoot },
      { name: 'Snapshot-Date', value: new Date().toISOString() },
    ],
  });

  await worm.write({
    event_kind: 'VAULT_ARCHIVED',
    payload: { arweave_tx_id: txId, merkle_root: merkleRoot },
  });

  return txId;
};

The Arweave transaction ID is a permanent reference: “the complete state of the vault as of this date is at tx_abc...”. Anyone with the transaction ID can retrieve and verify the complete vault history independently — no ClawQL required, no VG required. The permanence guarantee is the same as for immutable software releases.


Configuration: CLAWQL_MEMORY_BACKEND=git

Setting CLAWQL_MEMORY_BACKEND=git switches from pure R2 object storage to the Git-native vault:

# ~/.ClawQL/.env
CLAWQL_MEMORY_BACKEND=git
CLAWQL_MEMORY_GIT_REMOTE=git+tailscale://clawql-vg.tailnet-name.ts.net/org/agent-memory.git
CLAWQL_MEMORY_GIT_BRANCH=main
CLAWQL_MEMORY_GIT_PUSH_MODE=async
CLAWQL_MEMORY_GIT_COMMIT_ON=ingest        # ingest | session_end | manual
CLAWQL_MEMORY_GIT_AUTH=spiffe

# Durability
CLAWQL_MEMORY_GIT_BACKUP_R2=1
CLAWQL_MEMORY_GIT_BACKUP_R2_BUCKET=org-vault-backup
CLAWQL_MEMORY_GIT_ARWEAVE_ARCHIVE=weekly

# OKF v0.2
CLAWQL_MEMORY_OKF_VERSION=0.2
CLAWQL_MEMORY_STALE_AFTER_DAYS=90
CLAWQL_MEMORY_REQUIRE_WORM_REF=true

The memory_ingest flow with Git backend:

Agent calls memory_ingest({ type: "decision", ... })
  → clawql-memory validates against OKF v0.2 schema
  → writes .cqk file to ~/.ClawQL/vault/decisions/
  → updates index.md and log.md
  → git add + git commit (synchronous — ~50ms)
  → WORM write (synchronous)
  → git push to VG via Tailscale (async fork — agent never blocked)
  → R2 backup on VG post-receive (async)
  → NATS publish: clawql.memory.ingest (notifies swarm)
  → return MemoryEntry to agent

The memory_recall flow:

Recall is unchanged. The git repository is the local working copy — reads go directly to the filesystem. The five-layer recall stack (index survey, FTS, vector recall, PageIndex, Onyx) operates on the local copy. Network round-trips only happen on sync, not on read.

clawql sync with git backend:

# Pull latest knowledge from the team vault
clawql sync pull
# Equivalent to: git pull --rebase origin main
# Plus: rebuild vector index for new entries

# Push local knowledge to the team vault
clawql sync push
# Equivalent to: git push origin main
# (happens automatically on ingest — manual push also available)

# Check sync status
clawql sync status
# N commits ahead, M commits behind, K conflicts pending

PR Review for High-Stakes Knowledge

Not all knowledge should enter the shared vault automatically. High-stakes entries go through PR review in the Command Deck:

const ingestWithReview = (entry: KnowledgeEntry) =>
  Effect.gen(function* () {
    if (entry.requires_review || isHighStakes(entry)) {
      const branch = `memory/review/${entry.type}/${slugify(entry.title)}`;
      yield* git.checkout('-b', branch);
      yield* git.commit(filePath, commitMessage(entry));
      yield* git.push('origin', branch);

      // Open PR in Command Deck
      yield* commandDeck.createPR({
        title: `[memory] ${entry.type}: ${entry.title}`,
        body: prBody(entry),
        head: branch,
        base: 'main',
        labels: entry.tags,
        assignees: reviewersFor(entry.tags),
      });

      yield* worm.write({
        event_kind: 'MEMORY_PENDING_REVIEW',
        payload: { entry_path: filePath },
      });
    } else {
      yield* commitAndPush(entry);
    }
  });

The entry doesn’t appear in recall results until the PR merges. The review is logged in both the Command Deck PR history and the WORM audit trail.

Protected branch configuration in the VG:

# VG vault configuration
vault:
  branch_protection:
    main:
      required_approvals: 1
      auto_approve_paths:
        - personal/* # personal namespace — auto-approve
        - context/* # session context — auto-approve
      require_approval_paths:
        - decisions/* # decisions — require review
        - org/* # org-level standards — require review

PorTAL and the Fine-Tuning Flywheel

Ramp Labs open-sourced PorTAL — a framework for portable task-specific LoRA adapters with shared task-latent representations. The connection to Git-native memory is direct.

The Fine-Tuning Flywheel exports verified knowledge entries from the vault as training data. With PorTAL, the shared task-latent representation trains once from the vault’s verified entries. Refitting to a new base model requires only the lightweight per-base alignment update — not full retraining from the exported corpus.

The Git-native vault makes the training corpus deterministic and verifiable. Every training example is a git commit. The export command records the commit hash in the WORM manifest:

clawql inference export \
  --verdict passed \
  --format openai-jsonl \
  --vault-ref $(git -C ~/.ClawQL/vault rev-parse HEAD) \
  --output ./training-data/2026-07-28.jsonl

The --vault-ref flag records exactly which vault state the training data was derived from. A year from now, when investigating why a fine-tuned model behaves a certain way, you can check out the vault at that exact commit and see precisely what knowledge it was trained on. PorTAL’s portability means this verifiable corpus transfers across model families with minimal additional cost.


Comparing Approaches

Cloud memory (ChatGPT, Claude Projects)Pure R2 object storageClawQL — GitHub remoteClawQL — self-hosted VG
You own the historyNo — vendor owns itYesYesYes
Rollback to any point in timeNoNoYes — git revertYes — git revert
Human review before knowledge mergesNoNoYes — GitHub PRsYes — Command Deck PRs
Works across machinesVendor-managedYes, via syncYes — clawql syncYes — clawql sync
Works offlineNoNo (read cache only)Yes — local commitsYes — local commits
Conflict resolutionSilent overwriteSilent overwriteExplicit merge (git)Explicit merge (git)
OKF v0.2 trust signalsNoOptionalFirst-class (.cqk)First-class (.cqk)
WORM audit trailNoYes (ClawQL)Yes (ClawQL + git)Yes (ClawQL + git)
Long-term durabilityVendor-dependentR2 reliabilityR2 via GitHub ActionsR2 via post-receive
Semantic searchVendor-managedYes (five-layer stack)Yes (five-layer stack)Yes (five-layer stack)
Readable without vendor SDKNoPartiallyYes — plain MarkdownYes — plain Markdown
Extra infra to operateNone — vendor runs itNoneNone — use GitHubThin HTTP backend in VG
Air-gapped deploymentsNoPartiallyNoYes

The bottom two rows capture the trade-off precisely. GitHub remote means zero extra infrastructure — use what you already pay for, with GitHub’s reliability and familiar PR review. Self-hosted means air-gap capable and no data touching GitHub’s servers, at the cost of the thin HTTP backend in the VG.

Lesson: the right question is not “which tool is most convenient today” but “which tool gives you the most leverage as the vault grows over years.” Git’s history, rollback, and merge semantics compound in value the larger the vault gets. Both modes deliver this. Pick the remote based on your infrastructure constraints, not the vault format.


Honest Failure Modes

GitHub goes down (Mode A). Edge gateways continue working with their local vault copies — reads and writes are local. Ingests accumulate as local commits. When GitHub is restored, pending commits push automatically. GitHub’s 99.9%+ uptime means this is rare, but the local-first design means it’s never blocking.

The VG host goes down (Mode B). Edge gateways continue working with their local vault copies. Ingests accumulate as local commits. When the VG comes back, git push sends the backlog. The R2 bundle backup is the recovery path if the VG’s storage is lost — git clone r2://org-vault-backup/git-vault/latest.bundle restores the complete history.

Two agents produce a merge conflict that can’t be auto-resolved. The conflict surfaces explicitly in the Command Deck. A human resolves it. This is the correct behavior: conflicting knowledge entries about the same topic deserve human judgment. Silent overwrite is worse in every way. Frequency in practice: very low. Knowledge entries rarely conflict because different agents typically work in different directories.

The git repository grows very large. A vault with 100,000 entries and 5 years of history will have a repository in the 500MB-2GB range (Markdown compresses extremely well). Entirely manageable for git. If the repository becomes unwieldy, entries older than N years archive to an archive branch and the main branch stays lean.

Vector index goes stale after sync pull. When clawql sync pull brings in new entries from teammates, the local vector index doesn’t automatically know about them. clawql memory rebuild-index regenerates from the current vault state. For typical team sync sizes — tens to hundreds of new entries — this completes in seconds.

An agent ingests wrong or hallucinated knowledge. git revert <commit> removes the entry. The commit history records that it existed and was removed. The WORM log records the revert. If the wrong entry was already synced and already used in a fine-tuning export, the --vault-ref on the export command means future exports from the corrected vault exclude it. Corrections are auditable, not invisible.


Getting Started in One Afternoon

Step 1: Initialize the vault as a Git repository

clawql memory init --backend git
# Creates ~/.ClawQL/vault/ as a git repository
# Generates .gitattributes with union merge for index.md and log.md
# Creates initial index.md and log.md
# Create private repo: github.com/org/agent-memory
# Create GitHub App with write:contents on that repo only

export CLAWQL_MEMORY_BACKEND=git
export CLAWQL_MEMORY_GIT_REMOTE=https://github.com/org/agent-memory.git
export CLAWQL_MEMORY_GIT_AUTH=github_app
export CLAWQL_MEMORY_GIT_COMMIT_ON=ingest
export CLAWQL_MEMORY_GIT_PUSH_MODE=async
export CLAWQL_MEMORY_OKF_VERSION=0.2

# Clone from GitHub
clawql memory clone

# Add vault-backup.yml to .github/workflows/ in the repo
# (the GitHub Actions workflow from the Mode A section above)

# Register GitHub webhook on the repo
# Payload URL: https://your-vg.tailnet-name.ts.net/webhooks/github-vault
# Secret: $CLAWQL_GITHUB_WEBHOOK_SECRET
# Event: push

Step 2B: Self-hosted VG vault (air-gapped or strict sovereignty)

# Helm values
clawql-vault:
  enabled: true
  transport: tailscale
  auth: spiffe
  bare_repo_path: /vault/org/agent-memory.git
  backup:
    r2_bundle: true
    r2_bucket: ${CLAWQL_R2_BUCKET}/git-vault
    arweave_schedule: weekly
  nats_publish: true

helm upgrade --install clawql clawql/clawql \
  -f values.yaml \
  --set clawql-vault.enabled=true

# On each edge gateway:
export CLAWQL_MEMORY_GIT_REMOTE=git+tailscale://clawql-vg.tailnet-name.ts.net/org/agent-memory.git
export CLAWQL_MEMORY_GIT_AUTH=spiffe
clawql memory clone

Step 3: Test an ingest (same for both modes)

# Start the gateway
clawql inference serve --port 8080

# In your IDE (Cursor/Claude Code):
# "Remember: we chose JWT over sessions — stateless, horizontal scaling"

# Verify the commit was made locally
git -C ~/.ClawQL/vault log --oneline -1
# feat(memory): ingest decision/Authentication: JWT over sessions

# Verify it pushed to remote
clawql sync status
# Up to date with origin/main

Step 4: Enable OKF v0.2 trust signals

export CLAWQL_MEMORY_STALE_AFTER_DAYS=90
export CLAWQL_MEMORY_REQUIRE_WORM_REF=true

clawql memory lint
# Checks: required worm_ref, valid status enum, stale_after in future

From this point, every memory_ingest call is a git commit, pushed to GitHub or the VG vault, backed up to R2, and optionally archived to Arweave. The five-layer recall stack operates on the local working copy. Mode A teams need zero additional infrastructure beyond what they already have.


The Competitive Position

The X post that inspired this architecture recommended running a standalone self-hosted Git server. That’s a legitimate approach — but it means operating additional infrastructure: another service to monitor, another process to secure, another thing to keep updated, another dependency in your stack.

ClawQL’s approach: two modes, one vault format, zero separate servers. Teams on GitHub use GitHub as the remote — no extra infra, no new service to operate, PR review in the GitHub UI they already know. Teams with air-gap requirements or strict data sovereignty use the VG’s thin git HTTP backend — same vault behavior, self-contained, no forge overhead. Either way, the five-layer recall stack, WORM audit, OKF v0.2 trust signals, and the Fine-Tuning Flywheel are identical.

For teams that want the git-native memory pattern without running additional infrastructure, the GitHub remote mode is the answer. For teams that need to keep everything off GitHub, the self-hosted mode is the answer. ClawQL ships both.

Cloud memory products (ChatGPT memory, Claude Projects) store your organizational intelligence on vendor infrastructure in proprietary formats that you can’t export, roll back, or read without the vendor’s SDK. ClawQL stores it in a git repository you own, in plain Markdown that any text tool can read, backed up to R2 you control, with Arweave permanence for the entries that matter for the next decade.

No vendor has your knowledge. No vendor can hold it hostage.


Conclusion: Own the History

The agent memory problem is ultimately a data ownership problem. Every session your agent spends re-discovering what it already knew last week is a session paying the institutional knowledge tax — the cost of not owning your agents’ accumulated intelligence.

Git-native memory with ClawQL is the architecture that makes that intelligence a first-class, versioned, reviewable, rollbackable asset. Not a feature of a cloud product. Not a vendor-managed memory store that disappears when you cancel your subscription. A git repository you own, that your agents write to automatically, that your team reviews through GitHub PRs or the Command Deck, and that R2 backs up after every push — with zero extra servers to operate if you’re already on GitHub.

The OKF v0.2 trust signals turn each entry from a Markdown file into a governed knowledge artifact with explicit provenance, freshness, and lifecycle. The WORM audit trail makes every ingest and every recall auditable in ways no cloud memory product provides. The five-layer recall stack makes the vault searchable in ways that git alone cannot.

Your agents have been writing to /dev/null between sessions. Give them a git repository — and keep it yourself.


The Git-native vault backend, VG vault service, and OKF v0.2 frontmatter are documented at docs.clawql.com/learn/memory. The five-layer memory stack this extends: The Complete Agent Memory Stack. The immutable release architecture this connects to: Immutable Releases. Enterprise Ontology and .cqk format: Enterprise Ontology.

About the author

Daniel Smith builds ClawQL, an agent operating system for token-efficient discovery and execution over APIs — with observability, hardened tool boundaries, and production routing for LLM workloads. He writes here about the systems problems behind shipping agents.