Skip to main content
The backend ingests anchor data from connected repositories. It has no direct git access - it reads data exclusively through platform APIs (GitHub, GitLab, Bitbucket) triggered by webhooks.

Remote API Surface

The CLI calls these endpoints. There is no cloud upload/ingest pipeline - team sync is Git-first via the orphan branch.
EndpointMethodAuthRequiredPurpose
/anchors/searchPOSTBearer tokenYesSearch anchors/sessions with memory + answer synthesis
/anchors/deltaPOSTBearer tokenYesCompare two anchors (what changed between them)
/anchors/healthGETNoneNoHealth check (recommended convention, not called by CLI)

Self-Hosting

Point your CLI at your own server:
oobo settings set api_url https://oobo.mycompany.com
Your backend implements endpoints under /anchors.

POST /anchors/search

Search across anchors, sessions, and semantic memory. Returns full-text and memory hits, with an optional synthesized answer.

Request

{
  "query": "why did we build the auth middleware?",
  "since": "7d",
  "project": { "kind": "global", "value": null },
  "tool": "cursor",
  "limit": 20
}
FieldTypeRequiredDescription
querystringYesNatural-language search query
sincestring?NoTime filter (e.g. 24h, 7d, ISO-8601)
projectobject?NoScope: {"kind": "global"}, {"kind": "project_name", "value": "..."}, or {"kind": "project_id", "value": "..."}
toolstring?NoFilter by tool name
limitnumberNoMax results (default 20)

Response

{
  "answer": "The auth middleware was built to handle JWT refresh tokens because session-based auth had scaling issues.",
  "hits": [
    {
      "source": "memory",
      "snippet": "Team decided JWT with refresh tokens because session-based auth had scaling issues",
      "anchor_sha": "def456",
      "score": 0.87,
      "memory_id": "mem_abc123",
      "author": "teddy",
      "session_ids": ["sess-3", "sess-4"],
      "project": { "name": "oobo-agent" },
      "timestamp": 1773100000
    },
    {
      "source": "fts",
      "project": { "id": "p1", "name": "staging-only" },
      "anchor_sha": "abc123",
      "session_id": "sess-1",
      "tool": "cursor",
      "tokens": 12000,
      "timestamp": 1773282899,
      "intent": "fix auth middleware",
      "snippet": "auth middleware token refresh",
      "score": 0.91
    }
  ]
}
FieldTypeWhen presentDescription
answerstring?When memory hits exist and synthesis succeedsSynthesized 1-3 sentence answer to the query
hits[].sourcestringAlways"fts" (full-text search) or "memory" (semantic memory). Defaults to "fts" if absent
hits[].memory_idstring?Memory hits onlyUnique memory identifier
hits[].session_idsstring[]?Memory hits onlySessions that contributed to this memory
hits[].authorstring?Memory hits onlyWho created the anchor this memory came from
hits[].anchor_shastring?BothCommit SHA (may be short)
hits[].session_idstring?FTS hitsSingle session ID
hits[].toolstring?FTS hitsTool name
hits[].tokensnumber?FTS hitsToken count
hits[].intentstring?FTS hitsCommit intent
hits[].snippetstring?BothMatching text excerpt
hits[].scorenumber?BothRelevance score (0-1)
hits[].projectobjectBoth{ "id": "...", "name": "..." }
hits[].timestampnumber?BothUnix timestamp

POST /anchors/delta

Compare two anchors to see what changed: category shifts, complexity changes, new areas, new techniques, and a narrative summary.

Request

{
  "anchor_sha": "abc123",
  "previous_sha": null,
  "repo_id": null,
  "git_remote": null,
  "full": false
}
FieldTypeRequiredDescription
anchor_shastringYesCommit hash of the anchor to inspect
previous_shastring?NoExplicit previous anchor to compare against. If null, auto-finds the previous anchor
repo_idstring?NoFilter by repo ID
git_remotestring?NoFilter by git remote URL
fullboolNoIf true, includes detailed narrative, decisions, techniques, sessions

Response (compact, full: false)

{
  "current": {
    "sha": "abc123",
    "message": "feat: add JWT auth",
    "author": "teddy",
    "timestamp": "2026-05-05T10:00:00Z",
    "project": "oobo-agent",
    "headline": "Implemented JWT authentication with refresh token rotation",
    "category": "feature",
    "outcome": "success",
    "complexity": "moderate"
  },
  "previous": {
    "sha": "def789",
    "message": "refactor: extract auth middleware",
    "author": "teddy",
    "timestamp": "2026-05-04T16:00:00Z",
    "project": "oobo-agent",
    "headline": "Extracted auth logic into reusable middleware",
    "category": "refactor",
    "outcome": "success",
    "complexity": "low"
  },
  "changes": {
    "category_shift": { "from": "refactor", "to": "feature" },
    "complexity_shift": { "from": "low", "to": "moderate" },
    "new_areas": ["core/security", "redis"],
    "new_techniques": ["token rotation", "Redis TTL"],
    "files_continued": ["src/core/security/auth.py"],
    "files_new": ["src/core/security/refresh.py"],
    "narrative": "Built on the extracted middleware to add full JWT auth with refresh tokens..."
  }
}
When full: true, the response adds current_detail and previous_detail objects with: narrative, key_decisions, techniques, areas_affected, blockers, intent, reasoning, and session info.

Error Responses

StatusBodyDescription
404{"error": "anchor_not_found", "message": "..."}The specified anchor SHA was not found
500{"error": "internal_error", "message": "..."}Server error

The Orphan Branch

All anchor data lives on a Git orphan branch named oobo/anchors/v1. This branch has zero relationship to the repo’s code history. It contains only structured JSON metadata about commits enriched with AI session data, attribution, and transcripts. The CLI pushes to this branch on every git push (via a pre-push hook). No user action is required.

Configurable Anchor Remote

Anchors may not live in the same repository as the code. Users can configure a separate Git remote for anchor data via .oobo/config:
[anchors]
remote = "git@github.com:org/repo-anchors.git"
Or via the CLI: oobo settings project set remote <value>
When [anchors].remote is configured, the CLI pushes the oobo/anchors/v1 branch to that remote instead of origin. Your backend must handle this by:
  1. Monitoring .oobo/config on the default branch of connected repos for changes to [anchors].remote
  2. When [anchors].remote points to a separate repo, register webhooks on that repo and listen for pushes to oobo/anchors/v1 there
  3. When unset or set to a named remote (e.g. origin), anchors arrive via pushes to the same repository
The value can be either a named remote (e.g. oobo) or a full URL (e.g. git@github.com:org/repo-anchors.git). Your backend should resolve named remotes via the repository’s Git config or treat direct URLs as the target.

Push Behavior and Data Safety

ScenarioUser-Visible BehaviorData Loss?
Push succeedsSilent - no outputNo
Non-fast-forward (contention)Auto-retries up to 5 times with fetch+reconcileNo
Permission deniedWarning on stderr: failed to push oobo anchors: ...No - local branch intact
Remote doesn’t existWarning on stderrNo - local branch intact
Network failureWarning on stderrNo - local branch intact
The user’s git push is never blocked. Anchor push failures are warnings only. The local oobo/anchors/v1 branch always has the complete data - the next successful push will include all pending anchors.

Directory Layout (Sharding)

oobo/anchors/v1
├── README.md
├── c8/
│   └── e12fa9b3d4abcdef1234567890abcdef123456/
│       ├── metadata.json            ← Anchor (commit-level)
│       ├── timeline.json            ← Multi-agent interactions (optional)
│       ├── 1/
│       │   ├── metadata.json        ← SessionLink (per-session)
│       │   ├── transcript.json      ← Redacted transcript (if transparency=on)
│       │   └── subagents/
│       │       └── 1/
│       │           ├── metadata.json   ← Subagent type/ID
│       │           └── transcript.json
│       └── 2/
│           ├── metadata.json
│           └── transcript.json
└── ...
The commit hash (40-char hex SHA) is split: first 2 characters → top-level directory, remaining 38 → subdirectory name.

Webhook Trigger

Discovery: Where Do Anchors Live?

Before processing webhooks, the backend must determine where a project’s anchors are pushed:
  1. On repo connection, read .oobo/config from the default branch (if it exists)
  2. Check for [anchors].remote - if set, anchors are pushed to that repo/remote
  3. If unset, anchors are in the same repo on branch oobo/anchors/v1
  4. Subscribe to pushes on both the default branch (to detect config changes) and oobo/anchors/v1
  5. If a push to the default branch modifies .oobo/config, re-read [anchors].remote and update webhook registration accordingly

GitHub

Listen for push events where ref is refs/heads/oobo/anchors/v1:
{
  "ref": "refs/heads/oobo/anchors/v1",
  "before": "abc123...",
  "after": "def456...",
  "commits": [
    {
      "id": "def456...",
      "message": "oobo: add anchor for c8/e12fa9b3d4.../metadata.json",
      "added": ["c8/e12fa9b3d4.../metadata.json", "c8/e12fa9b3d4.../1/metadata.json"]
    }
  ]
}

GitLab

Listen for push events where ref is refs/heads/oobo/anchors/v1.

Bitbucket

Listen for repo:push events. Filter push.changes[].new.name == "oobo/anchors/v1".

Reading Files via Platform APIs

List all files (recursive tree):
GET /repos/{owner}/{repo}/git/trees/oobo/anchors/v1?recursive=1
Authorization: Bearer {token}
Read a single file:
GET /repos/{owner}/{repo}/contents/{path}?ref=oobo/anchors/v1
Authorization: Bearer {token}
Accept: application/vnd.github.raw+json
Compare two commits (incremental processing):
GET /repos/{owner}/{repo}/compare/{before}...{after}
Authorization: Bearer {token}

Ingestion Strategy

On Webhook Push

  1. Extract before and after SHAs from the webhook payload
  2. Get the diff between those two commits
  3. For each newly added XX/YYYY.../metadata.json (3 segments) → new anchor
  4. For each newly added XX/YYYY.../N/metadata.json (4 segments, N is numeric) → new session link
  5. For each newly added XX/YYYY.../N/transcript.json → session transcript
  6. For each newly added XX/YYYY.../timeline.json → multi-agent timeline

Pattern Matching

XX/YYYY.../metadata.json              → Anchor metadata
XX/YYYY.../N/metadata.json            → SessionLink (N is 1-indexed)
XX/YYYY.../N/transcript.json          → Session transcript
XX/YYYY.../N/subagents/M/transcript.json → Subagent transcript
XX/YYYY.../timeline.json              → Multi-agent timeline

Full Sync (Initial or Recovery)

  1. Fetch the recursive tree of oobo/anchors/v1
  2. Filter paths matching anchor metadata pattern
  3. Bulk-fetch all metadata files
  4. Process in parallel - anchors are independent

Rate Limits

  • GitHub REST: 5,000 requests/hour (authenticated)
  • GitHub GraphQL: 5,000 points/hour
  • Use blob SHAs for deduplication

Key Invariants

RuleDetail
One anchor per commitDirectory path is unique per commit hash
Sessions are 1-indexedSubdirectories are 1/, 2/, 3/
Append-onlyAnchors are never deleted from the branch
IdempotentRe-pushing the same anchor overwrites with identical content
Consistencyai_added + human_added == added always holds
Consistencyai_deleted + human_deleted == deleted always holds
OrderingSessions are ordered chronologically (1 = earliest)

Edge Cases

ScenarioBehavior
Branch doesn’t exist yetNo oobo data - skip until first push to oobo/anchors/v1
session_ids is emptyPure human commit (author_type = "human" or "automated")
ai_percentage is nullNo AI involvement - treat as 0%
is_estimated is trueToken counts were estimated via tiktoken (~90% accuracy)
link_type is "inferred"Session linked via time-window heuristic (±5min of commit)
Multiple anchors in one pushProcess all - user committed multiple times locally then pushed
Transparency mode mixedCheck transparency_mode per anchor - some have transcripts, some don’t
[anchors].remote configuredAnchors arrive via a different repo - backend must follow the remote
[anchors].remote changesRe-read .oobo/config on default-branch pushes, update webhook targets
Push failures (permissions etc.)Data stays on user’s local branch - next successful push catches up