Skip to main content

Branching & Isolation

HatiData V2 replaces V1's implicit branch fallback with explicit visibility modes — giving agents and reviewers precise control over what data they see.

Why Visibility Modes?

In V1, branch queries used a single rule: "read branch first, fall back to mainline." This caused subtle bugs:

  • Verification agents saw branch-scoped drafts instead of canonical mainline data
  • Merge conflicts were invisible until the merge attempt
  • Contract-driven agents couldn't guarantee they were reading the exact key they needed

V2 makes visibility explicit. Every memory read specifies a mode.

Visibility Mode Matrix

ModeReads FromFalls Back?Use Case
MainOnlyMainline (branch_id IS NULL)NoVerification agents, release decisions — need canonical truth
BranchLocalCurrent branch onlyNoIsolated experimentation, what-if scenarios
BranchWithFallbackBranch first, then mainlineYesNormal agent work on a branch — sees own changes + shared context
ExactKeyExact key match, no searchNoContract-driven agents — "give me proj:abc:api_contract or fail"

SDK Usage

from hatidata import VisibilityMode

# Verification agent: must see canonical state
results = client.search_memory(
query="architecture decisions",
visibility=VisibilityMode.MAIN_ONLY,
)

# Branch agent: see own work + shared context
results = client.search_memory(
query="build log",
branch_id="branch-feature-x",
visibility=VisibilityMode.BRANCH_WITH_FALLBACK,
)

# Contract agent: exact key or error
result = client.load_memory_exact(
key="proj:abc:api_contract",
visibility=VisibilityMode.EXACT_KEY,
)

SQL Syntax

-- MainOnly: explicit filter
SELECT * FROM agent_memories
WHERE project_id = 'proj-abc'
AND branch_id IS NULL;

-- BranchWithFallback: COALESCE pattern
SELECT * FROM agent_memories
WHERE project_id = 'proj-abc'
AND (branch_id = 'branch-feature-x' OR branch_id IS NULL)
ORDER BY branch_id NULLS LAST -- branch wins over mainline
LIMIT 1;

The "No-Leak" Guarantee

Branch-scoped data never appears in mainline queries. This is enforced at three layers:

Layer 1: Query Rewriting

The HatiData proxy rewrites queries based on visibility mode. A MainOnly query physically cannot return branch rows — the WHERE branch_id IS NULL clause is injected before execution.

Layer 2: ABAC Enforcement

Branch isolation is backed by ABAC policies. An agent with MainOnly access that attempts to read a branch-scoped key gets an explicit AccessDenied error, not empty results.

Layer 3: Merge Provenance

When branch data is merged to mainline, new rows are created — branch rows are never modified. This means:

  • The original branch data is preserved for audit
  • Merge creates a clear before/after trail
  • Rollback = delete the merged mainline rows (branch originals intact)

Creating and Managing Branches

Create a Branch

branch = client.create_branch(
project_id="proj-abc",
name="feature-auth-refactor",
parent_branch_id=None, # Fork from mainline
)
# branch.id = "branch-abc-123"

Write to a Branch

Memory writes automatically scope to the active branch:

client.store_memory(
key="proj:abc:api_contract",
value={"openapi": "3.1.0", "paths": {...}},
branch_id="branch-abc-123",
)
# Creates a branch-scoped row; mainline row unchanged

Inspect Branch Divergence

Before merging, review what the branch changed:

SELECT
memory_key,
'branch' AS source,
updated_at
FROM agent_memories
WHERE branch_id = 'branch-abc-123'

EXCEPT

SELECT
memory_key,
'mainline' AS source,
updated_at
FROM agent_memories
WHERE branch_id IS NULL
AND project_id = 'proj-abc';

Or use the convenience view:

SELECT * FROM v_branch_divergence
WHERE branch_id = 'branch-abc-123'
ORDER BY modified_at DESC;

Merge a Branch

merge_result = client.merge_branch(
branch_id="branch-abc-123",
strategy="last_writer_wins", # or "manual_review"
)
# merge_result.merged_keys = ["proj:abc:api_contract", "proj:abc:db_schema"]
# merge_result.conflicts = []

Merge strategies:

StrategyBehaviorUse Case
last_writer_winsBranch value overwrites mainlineMost agent workflows
manual_reviewCreates ReviewRequest for each conflictCritical artifacts

Branch Lifecycle with Tasks

When a task runs on a branch, all its artifacts are branch-scoped:

Task created (branch: feature-x)
└── Attempt runs
├── Reads: BranchWithFallback (sees branch + mainline)
├── Writes: Branch-scoped (only to feature-x)
└── Artifacts: branch-scoped instances

After the branch is merged, the artifacts become mainline-visible — but the original branch-scoped rows remain for audit.

Security Considerations

  • Branch deletion does not delete memory rows — rows are tombstoned for audit retention
  • Cross-branch reads are forbidden by default; require explicit ABAC policy grant
  • Nested branches (branch of a branch) are supported but limited to 3 levels deep
  • Concurrent merges to the same mainline key use optimistic locking — second merge gets a conflict error

Next Steps

Stay in the loop

Product updates, engineering deep-dives, and agent-native insights. No spam.