GNAP

March 12, 2026 · View on GitHub

Stars  Forks  MIT

GNAP — coordinate AI agents with just git

No servers. No databases. No vendor lock-in. Just git.

Your AI agents — OpenClaw, Codex, Claude Code, or custom — work as a team through a shared git repo. Four JSON files. That's the entire protocol.

GNAP Overview


Quickstart

Add a .gnap/ directory to any git repo:

.gnap/
  version              → "4"
  agents.json          → the team (humans + AI agents)
  tasks/FA-1.json      → first task
  runs/                → (empty — agents write here)
  messages/            → (empty — agents write here)

Commit, push. Agents pull, find their tasks, do the work, push results. That's the entire workflow.


How It Works

Every agent runs a heartbeat loop:

1. git pull
2. Read agents.json        → am I active?
3. Read tasks/             → anything assigned to me?
4. Read messages/          → anything new for me?
5. Do the work → commit → git push
6. Sleep until next heartbeat

Git history IS the audit log. No separate database needed.

┌──────────────────────────────────────────────────┐
│            Application Layer (optional)           │
│    budgets, dashboards, workflows, governance     │
├──────────────────────────────────────────────────┤
│            GNAP Protocol (this spec)              │
│         agents · tasks · runs · messages          │
├──────────────────────────────────────────────────┤
│            Git (transport + storage)              │
│     push/pull · merge · history · distribution    │
└──────────────────────────────────────────────────┘

Why GNAP

  • Zero infrastructure — no server to deploy, no database to maintain
  • Any agent, any runtime — if it can git push, it can participate
  • Auditable by defaultgit log is your audit trail
  • Human-in-the-loop — humans and AI agents are both first-class participants
  • Offline-capable — agents can work disconnected and sync later
  • Composable — build any application layer on top (budgets, workflows, dashboards)

Comparison

GNAPAgentHubPaperclipSymphonyCrewAI / LangGraph
Server requiredNoYes (Go)Yes (Node.js)YesYes (Python)
DatabaseNone (git)SQLitePostgreSQLIn-memoryIn-memory
Vendor lock-inNoneNoneNoneLinear + CodexLangChain / OpenAI
Setup time30 seconds5 min30 min30 min15 min
Task trackingYesNoYesExternal (Linear)No
Cost trackingYes (runs)NoYesYesNo
Agent-to-agent messagingYesYes (channels)LimitedNoNo
Human + AI agentsYesYesYesNoNo
Works offlineYesNoNoNoNo

The Protocol

GNAP defines exactly four entities:

#EntityFileWhat it does
1Agentagents.jsonWho is on the team
2Tasktasks/*.jsonWhat needs to be done
3Runruns/*.jsonAn attempt to complete a task
4Messagemessages/*.jsonCommunication between agents

Everything else is application layer — not part of the protocol.

Directory Structure

.gnap/
  version            ← protocol version (e.g. "4")
  agents.json        ← the team
  tasks/             ← work items (FA-1.json, FA-2.json, ...)
  runs/              ← execution attempts (FA-1-1.json, FA-1-2.json, ...)
  messages/          ← communication (1.json, 2.json, ...)

Protocol Version

.gnap/version contains the protocol version as a plain integer (e.g. 4). Agents SHOULD check this file on startup and refuse to operate if the version is higher than they support.


1. Agent

A human or AI participant registered in agents.json.

{
  "agents": [
    {
      "id": "carl",
      "name": "Carl",
      "role": "CRO",
      "type": "ai",
      "status": "active"
    },
    {
      "id": "leo",
      "name": "Leonid",
      "role": "CTO",
      "type": "human",
      "status": "active"
    }
  ]
}

Required fields:

FieldTypeDescription
idstringUnique identifier
namestringDisplay name
rolestringJob title or responsibility
typeenumai | human
statusenumactive | paused | terminated

Optional fields:

FieldTypeDescription
runtimestringopenclaw / codex / claude / custom
reports_tostringAgent ID of manager. Creates org tree
heartbeat_secintegerPoll interval in seconds. Default: 300 (5 min)
contactobjectPlatform handles (telegram, email, etc.)
capabilitiesarrayFree-form capability tags

Reserved: Agent ID * is reserved for broadcast and MUST NOT be used as an identifier.


2. Task

A unit of work. One JSON file per task.

File: .gnap/tasks/{id}.json

{
  "id": "FA-1",
  "title": "Set up Stripe billing",
  "assigned_to": ["leo"],
  "state": "in_progress",
  "priority": 0,
  "created_by": "ori",
  "created_at": "2026-03-12T11:40:00Z"
}

Required fields:

FieldTypeDescription
idstringUnique identifier (matches filename)
titlestringWhat needs to be done
assigned_toarrayAgent IDs responsible
stateenumSee state machine below
created_bystringAgent ID who created it
created_atISO 8601When created

Optional fields:

FieldTypeDescription
parentstringTask ID of parent task (creates subtask hierarchy)
descstringLonger description
priorityinteger0 = highest
dueISO 8601Deadline
blockedbooleanIs this blocked?
blocked_reasonstringWhy blocked
reviewerstringAgent ID who reviews
updated_atISO 8601Last modified
tagsarrayFree-form labels
commentsarrayList of { by, at, text } comment objects

Task States

backlog → ready → in_progress → review → done
            ↑          ↑           │
            │          └───────────┘  (reviewer rejects)

         blocked → ready              (unblocked)

         cancelled
StateMeaning
backlogNot yet prioritized
readyPrioritized, waiting for agent to pick up
in_progressAgent is working on it
reviewWork done, waiting for review
doneCompleted (terminal)
blockedCannot proceed (see blocked_reason)
cancelledWill not be done (terminal)

Reverse transitions:

  • review → in_progress — reviewer rejects, agent reworks
  • blocked → ready — unblocked, agent picks up again

3. Run

A single attempt to work on a task. One JSON file per run.

Tasks can have many runs. A failed run doesn't fail the task — the agent (or another agent) can create a new run.

File: .gnap/runs/{task-id}-{attempt}.json

{
  "id": "FA-1-1",
  "task": "FA-1",
  "agent": "carl",
  "state": "completed",
  "attempt": 1,
  "started_at": "2026-03-12T12:30:00Z",
  "finished_at": "2026-03-12T12:35:00Z",
  "tokens": { "input": 12400, "output": 3200 },
  "cost_usd": 0.08,
  "result": "Stripe account created, test mode live"
}

Required fields:

FieldTypeDescription
idstringUnique identifier (matches filename)
taskstringTask ID this run belongs to
agentstringAgent ID who executed
stateenumrunning | completed | failed | cancelled
started_atISO 8601When started

Optional fields:

FieldTypeDescription
attemptintegerAttempt number (1-based)
finished_atISO 8601When finished
tokensobject{ input, output } token counts
cost_usdnumberCost of this run
resultstringHuman-readable outcome
errorstringError message if failed
commitsarrayGit commit SHAs produced
artifactsarrayPaths to files produced by this run

Runs give you: cost tracking (budget = sum of runs), retry history (all attempts, not just final state), audit (who did what, when, how much it cost), performance (compare agents by speed/cost/success).


4. Message

Communication between agents. One JSON file per message.

File: .gnap/messages/{id}.json

{
  "id": "1",
  "from": "ori",
  "to": ["carl"],
  "at": "2026-03-12T09:30:00Z",
  "type": "directive",
  "text": "Focus on billing first. Everything else can wait."
}

Required fields:

FieldTypeDescription
idstringUnique identifier
fromstringSender agent ID
toarrayRecipient agent IDs. ["*"] = broadcast
atISO 8601Timestamp (MUST be present)
textstringMessage content

Optional fields:

FieldTypeDescription
typestringdirective | status | request | info | alert
channelstringTopic channel (e.g. sales, infra, general)
threadstringMessage ID this replies to
read_byarrayAgent IDs who have read this

Transport

Commit Convention

<agent-id>: <action> [details]

Examples:

carl: done FA-1 — Stripe test mode live
ori: create FA-3 onboarding-v2
leo: assign FA-1 to carl

Consistency

  • Model: Eventual consistency, bounded by max heartbeat interval
  • Conflicts: Standard git merge. If conflict, pull + rebase + retry push
  • Ordering: at field in messages, created_at/updated_at in tasks

Onboarding

Any agent that can read and write git can join — OpenClaw, Codex, Claude Code, custom bots, or a human with a terminal.

  1. Register — add entry to agents.json with status: active
  2. Grant access — give the agent git read/write (SSH key, PAT, or equivalent)
  3. Create first task — a check-in task in tasks/ assigned to the new agent
  4. Agent picks up — on next heartbeat, agent reads agents.json, finds the task, completes it, commits, pushes

See ONBOARDING.md for the detailed guide.


Application Layer

GNAP is a protocol — it defines entities and transport, not business logic. Applications built on top may add company goals, budgets, workflows, dashboards, integrations, and governance. These are not part of the protocol.


How it compares

ServerDatabaseConfigInfrastructure
GNAP❌ None❌ None4 JSON filesgit push
AgentHub (Karpathy)Go binarySQLiteAPI configSelf-hosted
CrewAIPython processIn-memoryPython codepip install
PaperclipNode.jsPostgreSQLYAML + DBDocker
SymphonyElixir daemonIn-memoryConfigMix install

Used in production

GNAP coordinates the AI team at Farol Labs — 4 agents (2 AI + 2 human) sharing 50+ tasks through a single git repo.

Live dashboard →

Contributing

See CONTRIBUTING.md.

License

MIT