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.
Patterns
There are two common patterns for multi-agent memory sharing:
Pattern
When to use
Shared scope
All agents contribute to and read from a common project scope
Supervisor + workers
A supervisor agent coordinates several worker agents, each with its own user/agent scope
Pattern 1: shared project 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.
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.
Pattern 2: supervisor and workers
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.
asyncdefrun_worker(topic: str,worker_id: str,client: AsyncSpectron) -> str: worker_scope=[*SUPERVISOR_SCOPE,f"agent/{worker_id}"] asyncwithclient.sessions.create(scope=worker_scope)assession: # Worker researches its topic context=awaitsession.recall(query=topic,top_k=5) findings=awaitllm.research(topic,context.formatted) # Worker stores findings in its own scope awaitsession.remember(findings,memory_category="knowledge") # Return a summary for the supervisor reflection=awaitsession.reflect( query=f"Summarise findings about {topic}", persist=False, ) returnreflection.summary
asyncdefsupervisor(client: AsyncSpectron): topics=["pricing trends","competitor features","customer sentiment"] # Run workers concurrently results=awaitasyncio.gather(*[ run_worker(topic,f"worker-{i}",client) fori,topicinenumerate(topics) ]) # Supervisor aggregates findings into shared scope asyncwithclient.sessions.create(scope=SUPERVISOR_SCOPE)assession: fortopic,findinginzip(topics,results): awaitsession.remember( content=f"[{topic}] {finding}", memory_category="knowledge", ) # Supervisor synthesises and stores a final report awaitsession.reflect( query="Synthesise all research findings into a coherent executive summary.", persist=True, )
Scope hierarchy
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.
Preventing memory pollution
When many agents write to a shared scope, unrelated facts from different tasks can accumulate. Use metadata to tag memory items with their provenance:
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 awaitsession.remember( content="The enterprise customer segment prioritises SOC2 compliance over cost.", )
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=awaitsession.get_state(scope=SHARED_SCOPE) forentityinstate.entities: forattrinentity.attributes: ifattr.superseded_by: print(f"Conflict: {entity.name}.{attr.key} was {attr.value} → now {attr.superseded_by.value}")
Supervisor reflection
The supervisor pattern works best with a dedicated reflection pass that runs after all workers complete:
asyncwithclient.sessions.create(scope=SUPERVISOR_SCOPE)assupervisor_session: # Load all worker contributions full_context=awaitsupervisor_session.profile() # Synthesise synthesis=awaitsupervisor_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.