Skip to content

Team coordination

Memoturn’s coordination surface is what single-vendor memory (Claude’s, Cursor’s) structurally can’t do: every agent on the same project, regardless of vendor, sees the same locks, reviews, presence, and focus state in real time.

Each primitive is a small MCP tool. The combinations are what matters.

Two agents both decide to refactor apps/api/src/auth.ts. Without coordination they collide. With claim, the first agent acquires a TTL-bounded lease on file:apps/api/src/auth.ts; the second agent’s claim attempt fails-loud with the current held_by and expires_at, and backs off cleanly.

const a = await mt.claim({
key: "file:apps/api/src/auth.ts",
holder: "cursor@alice",
ttl_ms: 10 * 60 * 1000,
});
if (!a.ok) {
console.log(`held by ${a.held_by}, retry after ${new Date(a.expires_at)}`);
return;
}
// ... do the work ...
await mt.release("file:apps/api/src/auth.ts", "cursor@alice");

Optional defense in depth: gate every write with record_turn’s requires_claim. With claim_policy: "hard" the engine refuses the write if the lease lapsed or a different holder owns it. Default soft lets the write through but stamps claim_violation metadata and emits a broadcast: enough visibility for incremental adoption without breaking existing flows.

request_review opens a typed gate; downstream agents subscribe to review_completed (or poll list_pending_reviews) and only proceed on approved. Idempotent: a racing reviewer that loses gets ok=false, reason: "already_decided" with the current decision, so two reviewers can’t both think they shipped.

const r = await mt.requestReview({
subject: "refactor: auth middleware",
claim_key: "file:apps/api/src/auth.ts",
reviewers: ["claude-code-tests@bot", "alice@example.com"],
deadline_ms: 30 * 60 * 1000,
});
// Reviewer agent (or human) decides:
await mt.markReviewed(r.review.id, "approved", "lgtm");

decided_by and decided_at are first-class on every review row, so multi-agent workflows have an audit trail of who approved what.

list_focus returns the top files by team activity in the window (default 1 h). Each row carries the path, the turn count, and the distinct (tool, actor) pairs touching the file, so an agent can detect a cross-tool collision before reaching for claim.

const focus = await mt.listFocus({ since_ms: 60 * 60 * 1000 });
for (const file of focus.files) {
if (file.agents.length > 1) {
console.warn(
`collision risk on ${file.path}:`,
file.agents.map((a) => `${a.tool}@${a.actor}`).join(", "),
);
}
}

The dashboard’s /projects/{slug}/focus route renders this with badges on multi-agent files. That’s the screenshot meant to land in your team’s standup.

heartbeat on a 30-second cadence carries optional intent fields (tool, current_file, current_function, current_action). They fan out on presence_updated so the dashboard paints a live “what is each agent doing right now” view without polling.

setInterval(() => {
void mt.heartbeat({
tool: "cursor",
current_file: "apps/api/src/auth.ts",
current_function: "signupWithPassword",
current_action: "editing",
});
}, 30_000);

Rows are keyed on (tool, user_email) so the same human running both Cursor and Claude Code shows two distinct rows. Idle dim past 90 s; row drops past 5 min.

To drive presence from editor hooks instead of a timer, use npx @memoturn/cli hooks presence (stdin is one hook JSON payload, output is a Memoturn heartbeat), or run memoturn connect hooks once to merge starter snippets into local Cursor / Claude Code / Codex hook configs. Vendor docs: Claude Code hooks, Cursor hooks, Codex hooks.

pin_memory accepts derived_from_turn_ids: up to 50 source turns the decision was extracted from. The dashboard’s decisions view renders the trail so a future reader can see the conversation that produced the pin.

await mt.pinMemory({
content: "Decision: use Hyperdrive for connection pooling. Rejected pgBouncer due to ops overhead.",
tags: ["decision", "infra"],
derived_from_turn_ids: [turn1.id, turn2.id, turn3.id, turn4.id],
});

Ids that don’t resolve to turns in this project are dropped (reported on derived_from_dropped) rather than rejecting the pin. A caller-side caching error doesn’t kill an audit trail.

  • Tool reference: every parameter, every default
  • Memory rules: declarative automation on coordination broadcasts (claim_expired, review_completed, …)