Build

Multi-agent shared memory

Supervisors, reflection, and shared scopes.

When multiple agents collaborate on a shared task, they need access to the same memory. Spectron's scope model makes this natural: agents that share a scope dimension (such as project) can all read from and write to the same experiential memory, while retaining individual isolation where needed.

There are two common patterns for multi-agent memory sharing:

PatternWhen to use
Shared scopeAll agents contribute to and read from a common project scope
Supervisor + workersA supervisor agent coordinates several worker agents, each with its own user/agent scope

The simplest approach – all agents are given the same scope when creating sessions. Memory written by one agent is immediately visible to others operating in the same scope.

from surrealdb import AsyncSpectron

client = AsyncSpectron(
context="acme-prod",
endpoint="https://spectron.example.com",
api_key="sk-...",
)

SHARED_SCOPE = ["org/acme/project/market-research-q3"]

# Agent A: data collection agent
await client.remember(
"Competitor X launched a new pricing tier at $299/mo targeting SMBs.",
scope=SHARED_SCOPE,
)

# Agent B: analysis agent – sees Agent A's memory
results = await client.recall(
"recent competitor pricing changes",
k=5,
scope=SHARED_SCOPE,
)
# results.hits includes Agent A's finding

To prevent worker agents from writing to shared memory (read-only readers), issue separate API keys with restricted write capabilities. A key with principal agent and a scope_floor of {org: "acme", project: "market-research-q3"} can read and write at that scope. If you need read-only agents, add scope floor restrictions at the application layer before passing context to the agents.

A supervisor agent orchestrates several workers. Each worker operates in its own user/agent scope, but the supervisor aggregates their findings into a shared project scope.

SUPERVISOR_SCOPE = ["org/acme/project/research-pipeline"]

async def run_worker(topic: str, worker_id: str, client: AsyncSpectron) -> str:
worker_scope = [*SUPERVISOR_SCOPE, f"agent/{worker_id}"]
async with client.sessions.create(scope=worker_scope) as session:
# Worker researches its topic
context = await session.recall(query=topic, top_k=5)
findings = await llm.research(topic, context.formatted)

# Worker stores findings in its own scope
await session.remember(findings, memory_category="knowledge")

# Return a summary for the supervisor
reflection = await session.reflect(
query=f"Summarise findings about {topic}",
persist=False,
)
return reflection.summary

async def supervisor(client: AsyncSpectron):
topics = ["pricing trends", "competitor features", "customer sentiment"]

# Run workers concurrently
results = await asyncio.gather(*[
run_worker(topic, f"worker-{i}", client)
for i, topic in enumerate(topics)
])

# Supervisor aggregates findings into shared scope
async with client.sessions.create(scope=SUPERVISOR_SCOPE) as session:
for topic, finding in zip(topics, results):
await session.remember(
content=f"[{topic}] {finding}",
memory_category="knowledge",
)

# Supervisor synthesises and stores a final report
await session.reflect(
query="Synthesise all research findings into a coherent executive summary.",
persist=True,
)

Spectron resolves scope visibility from OR-of-AND clauses on each record. For typical single-owner tags, a query at scope ["org/acme"] retrieves org-wide memory and shared org facts, but not another user’s private record info unless your grant covers them.

{org: "acme"}                    ← visible to all org agents
{org: "acme", project: "alpha"} ← visible to project alpha agents
{org: "acme", project: "alpha", agent: "planner"} ← planner only

A supervisor querying at {org: "acme", project: "alpha"} sees:

  • Everything at the project scope (shared findings)

  • Everything at more specific scopes (individual worker findings)

A worker querying at {org: "acme", project: "alpha", agent: "worker-1"} sees only its own memory and the shared project scope.

When many agents write to a shared scope, unrelated facts from different tasks can accumulate. Use metadata to tag memory items with their provenance:

await session.remember(
content="Customer segment 'enterprise' values compliance features most.",
memory_category="knowledge",
metadata={"source": "customer-interview-2024-q3", "agent": "interview-agent"},
)

Use the entity type system to keep memory structured. Rather than storing flat facts, extract structured entities so that conflicts are detected and superseded correctly:

# Good: structured entity extraction will happen automatically from this
await session.remember(
content="The enterprise customer segment prioritises SOC2 compliance over cost.",
)

# The extraction pipeline creates:
# entity: CustomerSegment/enterprise
# attribute: top_priority = "SOC2 compliance"
# relation: enterprise → values → compliance_features

When two agents write conflicting facts to the same scope at roughly the same time, Spectron's reconciliation pipeline detects the conflict and creates a supersession chain. The later write wins for attribute values. You can inspect the conflict:

state = await session.get_state(scope=SHARED_SCOPE)
for entity in state.entities:
for attr in entity.attributes:
if attr.superseded_by:
print(f"Conflict: {entity.name}.{attr.key} was {attr.value} → now {attr.superseded_by.value}")

The supervisor pattern works best with a dedicated reflection pass that runs after all workers complete:

async with client.sessions.create(scope=SUPERVISOR_SCOPE) as supervisor_session:
# Load all worker contributions
full_context = await supervisor_session.profile()

# Synthesise
synthesis = await supervisor_session.reflect(
query="What are the top-level conclusions across all worker findings? What is still uncertain?",
persist=True,
)

print(synthesis.summary)

The persisted reflection becomes part of the shared scope's long-term memory and is included in future profile() calls.

import { Spectron } from "@surrealdb/spectron";

const client = new Spectron({
endpoint: process.env.SPECTRON_ENDPOINT!,
context: "acme-prod",
apiKey: process.env.SPECTRON_API_KEY!,
});

const sharedScope = { org: "acme", project: "market-research-q3" };

await client.remember(
"Competitor X raised prices by 20% in Q2.",
{ scope: [...sharedScope, `agent/worker-1`] },
);

const results = await client.recall("competitor pricing", { k: 10, scope: sharedScope });
console.log(results.hits);

Was this page helpful?