Skip to content
CockroachDB × AWS — Build with Agentic Memory

Parallel agents that think for forty seconds without corrupting each other.

An LLM agent reads shared memory, thinks for forty seconds, then acts. The world changed while it was thinking. Today you get two bad options: lock everything, or throw the thinking away. INTERLOCK adds a third.

Free, self-serve, no signup — the key is issued by the same cluster this page runs on.

The finding that started this

Running AI agents in parallel today is slower than running them one at a time — and costs 83% more.

You pay nearly double to go backwards. That is not a tuning problem; it is the reason parallel agents do not scale.

Try it

Watch two agents collide

This runs against the production cluster, right now. Not a recording — real embeddings, a real serializable commit, a real ruling, and the cost of producing it.

Run a live conflict

Two agents, one queue. The Scheduler spends 12,500 tokens planning an overnight rebalance; Triage commits into the same queue while it is still thinking.

Nothing here is pre-recorded. Each run creates real rows, generates a real embedding, and calls a real model.

The problem

An agent's transaction is not a database transaction

A database transaction is measured in milliseconds and its read-set is knowable up front. An agent’s is measured in minutes of inference, and its read-set is broad and opaque. Classical concurrency control was not built for that shape.

  1. t = 0s

    1. Read

    The agent reads shared memory and forms a plan against that snapshot.

  2. t = 0–40sWorld changes

    2. Think

    Inference runs. Meanwhile another agent commits a write that touches the same state.

  3. t = 40sSilent corruption

    3. Act

    The agent acts on a world that no longer exists. Nothing raised an error.

Nothing in that sequence throws an exception. The agent did exactly what it decided to do, and what it decided to do was wrong. Monitoring built to catch crashes will report a zero error rate the whole way through.

What everyone does instead

The state of the art is a merge conflict

Parallel agents are shipping at scale right now — sub-agent teams, background agents, swarms coordinating a hundred agents on one job. The industry's answer to shared-state conflict is a git worktree.

Two-phase locking

Hold locks for the duration of the task.

An agent holds its locks for the entire minutes-long task, blocking every other agent that touches overlapping state.

0.81 deadlocks per trial · 1.04× speedup

Optimistic concurrency

Detect the conflict at commit and abort.

A single conflict discards the agent's entire reasoning — and re-running it costs more than the parallelism ever saved.

0.95 aborts per trial · 0.93× speedup at 1.83× cost

Fork and merge

Give every agent its own git worktree.

This is what mainstream systems actually ship. It offers only weak isolation, and if two agents edit the same file the merge simply fails.

Cannot prevent anomalies

The mechanism

Validation becomes reasoning; abort becomes repair

The insight is that most conflicts are semantically irrelevant. An agent can read the conflicting write and judge whether it actually breaks its plan. A real conflict needs repair of only the dependent steps — not the whole task.

  1. 1

    Declare

    Serializable write + vector column

    Before acting, an agent writes an intent: what it read, and what it plans to do — as text and as an embedding.

  2. 2

    Watch

    C-SPANN distributed vector index

    When any agent commits, we find which in-flight agents are actually threatened — by meaning, not by row overlap.

  3. 3

    Diff

    AS OF SYSTEM TIME

    Show the threatened agent exactly what changed in the world since the snapshot it read.

  4. 4

    Adjudicate

    Amazon Bedrock

    A model reads the diff and rules: irrelevant, invalidating, or fatal. Most conflicts are irrelevant.

  5. 5

    Commit

    SERIALIZABLE by default

    The final write lands as a genuine serializable transaction. Lost updates are impossible, not unlikely.

Our own audit feed disagrees with that — about seven in ten rulings on /v1/adjudications are invalidating. That feed is not production traffic: every row in it came from a demo, a test or the benchmark, and all three construct a real conflict on purpose. Nobody writes a demo where nothing happens. The claim rests on the benchmark workload, where writes are drawn realistically — and the endpoint returns this caveat next to the counts rather than waiting to be caught.

Why this database

Not convenient — load-bearing

Every one of these is doing work the system could not do without it. Swap the database and the mechanism stops functioning, rather than merely getting slower.

SERIALIZABLE by default

Two agents must never both win a write.

Correctness is inherited from the database rather than reimplemented in application code. PostgreSQL defaults to READ COMMITTED; most vector stores have no transactions at all.

MVCC + AS OF SYSTEM TIME

Diff the agent's snapshot against the present.

This is the mechanism, not a convenience. Without point-in-time reads there is no way to show an agent what changed under it, and no way to reconstruct a past decision during review.

C-SPANN vector index

Find semantically threatened agents.

Embeddings live in the same transactional database as the rows, so a similarity query and a graph query see identical state. A bolt-on vector store introduces exactly the consistency gap this system exists to close.

Multi-region survivability

Adjudication cannot stop when a region does.

Agents contend across regions. If the arbiter's memory is unavailable, every agent in the fleet is unsafe at once — so the memory has to survive a region loss without losing a decision.

Changefeeds

Notify without a second source of truth.

A changefeed on commit_log posts to Lambda, which publishes to EventBridge and on to an SQS worker. Publishing from the application instead would let 'the write succeeded' and 'the event was sent' disagree — a crash between them silently drops an adjudication. Reading the same durable log means an event exists if and only if the row does.

Benchmark

Where this pays, and where it doesn't

Every figure carries its provenance. Published means somebody else measured it and we cite them. Measured means our harness produced it on a live cluster, and you can re-run it.

Parallel vs serial speed

0.93×

Optimistic concurrency is slower than not parallelising at all

Published

Token cost of parallelism

1.83×

83% more inference spend, for negative speedup

Published

Failures from misalignment

>1/3

Share of multi-agent failures traced to inter-agent conflict

Published

Tokens burned after the warning

58.1%

Spend that continues after a run is already doomed

Published

Cost vs. optimistic concurrency

relative to serial execution · lower is better

Measured
INTERLOCKOptimistic concurrency
1×2×3×4×just retry — nothing worth protecting3.61×3k2.12×10k1.47×24k1.31×37kINTERLOCKOCCtokens of reasoning per task

Below roughly 12,000 tokens of reasoning per task, don’t use this — just retry. There is nothing expensive enough to be worth protecting. Above it, the saving grows with the cost of the task.

0 lost updates at every point on this curve, in every mode. Serializable isolation holds regardless of which approach is cheaper.

Reproduce this in fifteen seconds

Node only — no database, no AWS account, no configuration. It issues a throwaway key if you do not have one.

git clone https://github.com/usv240/interlock && cd interlock && npm install

npm run compare                     # two collisions, priced, ~15s
npm run compare -- --reasoning 400  # below the crossover — prints a loss

The second command is the one worth running. It sets the task below the crossover, where this approach costs more than simply retrying, and the output says so in red. A demo that can only produce good news is not evidence.

Head-to-head against both baselinesthroughput and token cost, at the top of the curve

Three ways to handle a conflict

Throughput

× serial execution speed

  • Two-phase locking1.04×
  • Optimistic concurrency0.93×
  • INTERLOCK1.40×

Dashed line marks 1.0× — running the agents one at a time. Further right is better.

Inference cost

× serial execution token spend

  • Two-phase locking1.00×
  • Optimistic concurrency1.83×
  • INTERLOCK1.28×

Dashed line marks 1.0× — running the agents one at a time. Further left is better.

The two baseline rows are measured numbers from CoAgent: Concurrency Control for Multi-Agent Systems (arXiv:2606.15376). The INTERLOCK row is our own measurement, produced by npm run bench on the live cluster. It is taken at the top of the crossover curve below, where tasks are expensive enough for the approach to pay — at smaller task sizes it loses, which the curve shows rather than hides.

Verified against the live cluster10 claims, each with the command that proves it

Every claim, and how to check it

ClaimValueHow it was checked
IsolationserializableSHOW default_transaction_isolation
Regions3 (us-east-1, us-east-2, us-west-2)SHOW REGIONS FROM DATABASE
Survival goalregionSHOW DATABASES
Time-travel reach1–24h, bounded by the GC windownpm run continuity
Embedding dimensions1024 (Titan V2)live Bedrock invocation
Vector index actually selectedyes, at ~1,700 in-flight plansnpm run ai:vector
Tenant isolation0.0000 cosine, still not matched across tenantsnpm run test:isolation
Spend ceilingenforced per tenant and service-wideGET /v1/health
Lost updates0 across every benchmark modecounter arithmetic
Exactly-once adjudicationheld under repeated connection killsnpm run chaos

Architecture

What each piece actually does

The hackathon asks for at least two CockroachDB tools and one AWS service. We use all four CockroachDB tools — and only the AWS services we genuinely run on, because claiming more than you use is worse than claiming less.

Two paths, separated on purpose

synchronousasynchronous
AGENT WAITSAGENT DOES NOT WAITAgent fleetyours, or oursLambda API/v1/intents · /v1/commitsdeclare + commitCockroachDB3 regions · SURVIVE REGION FAILUREintent + read-setSERIALIZABLEprovenance graphrecursive CTEembeddingsC-SPANN vector indexadjudicationUNIQUE = exactly-onceMVCC historyAS OF SYSTEM TIMEchangefeedcommit_log rows stream out of the same durable log the write went into/v1/cdcwebhook sinkEventBridgecommit.landedSQS3 retries → DLQWorker λbatched, parallelBedrockTitan + Claudeembed · adjudicatedetect

CockroachDB appears in both lanes because it is the only component that has to. It holds the state an agent commits, the provenance and vectors detection walks, the MVCC history the diff replays, and the ruling — and it is the thing that notifies, so the write and the notification cannot disagree.

What every service is doing, and why it is there4 CockroachDB tools · 7 AWS services

CockroachDB — all four tools

  • Distributed Vector Indexing

    A C-SPANN index over intent embeddings finds which in-flight agents are semantically threatened by a commit — the ones that share meaning but no rows.

  • Managed MCP Server

    Not a human console — a tool belt for the adjudicating agent. Before ruling, it may request one read-only lookup: has this resource been churning all morning, or is this the first change in an hour? Because the server is read-only by default, an agent investigating an incident is structurally incapable of altering it, and every lookup is audit-logged outside our own logging. A model that cannot look things up has to guess.

  • ccloud / Cloud control plane

    A continuity agent that refuses to let adjudication run if resilience is not actually configured. It reads cluster state from the control plane, verifies three regions and a region-survival goal, and reports gc.ttlseconds per table — the real ceiling on how far back a diff can read, which turned out to be 75 minutes by default and is now a day on the decision tables.

  • Agent Skills Repo

    We consume the published skills for schema and index design, and wrote one in return for serializable agent intents — skills/managing-long-running-agent-transactions. It ships in this repo and is not upstream; we would rather say that than imply a merge that has not happened.

AWS — only what we run on

  • Amazon Bedrock

    Titan Text Embeddings V2 for the vector path, and Claude on two caller-selectable tiers — pass `adjudicator` on a commit, named by role rather than model id so your code survives the id moving. The cheap tier is the default because the provenance graph has already narrowed the question before a model sees it.

  • AWS Lambda

    Two functions. The public API declares intents, commits and streams the demo. A separate SQS worker adjudicates off the queue with partial-batch failure reporting and its own concurrency.

  • Amazon EventBridge + SQS

    A CockroachDB changefeed on commit_log posts to the API, which publishes to EventBridge; a rule routes to SQS, and the worker adjudicates in parallel. Commits return as soon as they are durable instead of blocking on everyone they threatened. Three retries, then a dead-letter queue.

  • Amazon S3 + CloudFront

    Hosts this page as a static export with a private origin — CloudFront-only read via Origin Access Control, no public bucket policy.

  • Amazon CloudWatch

    The logs, which caught a cold-start failure and a cross-region IAM denial during build.

  • AWS Budgets

    A $20 monthly cap on the account, alerting at 50%, 80% and 100%. Alert-only by design, because Budgets can only email — the enforcement that actually refuses to spend sits in the API handler, ahead of every Bedrock call.

  • AWS IAM

    A runtime role scoped to nine specific model and inference-profile ARNs rather than bedrock:*, with explicit denies on deleting evidence and altering model access. Nine rather than two because a us. inference profile dispatches across regions, so least privilege means naming the underlying model in every region it may route to.

Use it

A referee for your agents, not a place to run them

You keep your models, your prompts and your tools. INTERLOCK arbitrates the state two agents both touch — and nothing else.

You keep

  • Your models, prompts and tools.
  • Your framework — LangChain or none.
  • Your data. We never see your agent’s reasoning.

You get

  • A referee for state two agents both touch.
  • A ruling naming which steps died, not just that something did.
  • Serializable commits, an audit feed, and the bill for each decision.

You do not get

  • Model access. This is not an inference proxy — you cannot send it a prompt.
  • Somewhere to host or run your agents.
  • A vector store you query directly.

Your agent today

// your agent, today
const state = await db.getQueue("support-eu");     // read
const plan  = await llm.plan(state);               // think, 40s
await db.setQueue("support-eu", plan.result);      // act

Reads at t=0, acts at t=40s. If anything moved in between, this overwrites it and nothing raises an error.

The same agent, refereed

// the same agent, refereed
const q     = await il.registerResource({ key: "support-eu" });
const plan  = await llm.plan(q.body);              // your model, untouched

const { intent } = await il.declare({              // + say what you're doing
  agentId, statement: plan.summary,
  reads: [{ resourceId: q.id, observedVersion: q.version }],
  steps: plan.steps,
});

const { adjudications } = await il.commit({        // + commit through us
  agentId, intentId: intent.id, resourceId: q.id,
  expectedVersion: q.version, body: plan.result,
  statement: "Rebalanced the overnight rota.",
});

for (const a of adjudications) {                   // + act on the ruling
  if (a.verdict === "invalidating") redo(a.affectedSteps);
}

The llm.plan call is still yours. Three additions: say what you are about to do, commit through us, act on the ruling.

Start here

The first three take about a minute and need nothing but a terminal.

  1. See that it is real

    No key needed. Returns the live topology, the survival goal, and how much quota is left today.

    # no key needed
    curl -s https://wpvk3ox2bxo2w3zhxmx54ssjf40rakuz.lambda-url.us-east-1.on.aws/v1/health
  2. Watch a real conflict get adjudicated

    Creates real rows, embeds a real statement, and calls a real model. The response is the full trace, including what it cost.

    curl -s -X POST https://wpvk3ox2bxo2w3zhxmx54ssjf40rakuz.lambda-url.us-east-1.on.aws/v1/demo \
      -H 'content-type: application/json' -d '{}'
  3. Point your own agents at it

    A key gives you an isolated tenant. No other caller’s commits can adjudicate your intents — the tenant filter sits inside the detection query, not around it.

    Free, instant, no signup. Generous per-address limit, reset daily.

  4. Run the whole loop in one command

    Two agents, one queue, a real ruling — declared, contended and adjudicated, printed step by step. Needs Node and nothing else: no database, no AWS account, no configuration. Pass your key, or leave it off and it issues a throwaway one.

    git clone https://github.com/usv240/interlock && cd interlock && npm install
    npm run quickstart -- ilk_your_key_here
Already on LangChain? Add a callback.one wrapper, no rewrite

A guard, not a rewrite

import { InterlockCallback } from "interlock/langchain";

const guard = new InterlockCallback({ apiKey, agentId, resources });
await executor.invoke(input, { callbacks: [guard] });

// names which steps died, not just that something did
if (guard.wasInvalidated) redo(guard.stepsToRedo);

Tool calls become plan steps as they happen, which is what lets a conflict be repaired at step granularity instead of throwing the task away.

Full API reference8 endpoints

Every endpoint

  • POST /v1/keysno quota

    Self-serve. Creates an isolated tenant and returns a key, shown once.

  • GET /v1/healthno quota

    Topology, survival goal, time-travel reach, and quota remaining.

  • POST /v1/agentsno quota

    Register an agent. Idempotent by name, so a fleet can call it on every boot.

  • POST /v1/resourcesno quota

    Register a piece of shared state for agents to contend over.

  • POST /v1/intents

    Declare what an agent is about to do, and what it read, before it acts.

  • POST /v1/intents/stepsno quota

    Add steps as a plan unfolds — for agents that discover their plan while working.

  • POST /v1/commits

    Commit a change. Returns a ruling for every agent the commit threatened.

  • GET /v1/adjudicationsno quota

    The audit feed — recent rulings, read-only.

Your agents keep their own reasoning and their own tools. INTERLOCK arbitrates only the shared state — declare an intent, commit through it, and act on the ruling.

Resilience

Kill a region mid-adjudication

If the memory arbitrating conflicts is unavailable, every agent in the fleet is unsafe at the same moment. So the test is not whether it works — it is whether it keeps working while a region is being taken away from it.

An agent whose memory goes offline does not degrade gracefully. It stops. That is the premise of this hackathon, and it is the one claim a demo can actually prove rather than assert.

SURVIVE REGION FAILUREexactly-once adjudicationno lost decisions