Multi-Tenant Primitives
The problem
A single Agentium process serving a SaaS product needs to:- Isolate tenants. Customer A’s memory must never bleed into customer B’s session.
- Isolate users within a tenant. Alice’s
userMemory.preferencesmust not be visible to Bob. - Share infrastructure. One model client, one StorageDriver, one process — but logically partitioned data.
AgentFactory/TeamFactory/WorkflowFactory— construct scope-aware agents per request.ScopedStorage— namespace every read/write on anyStorageDriverby tenant + user.
ScopedStorage
The lowest-level primitive. Wraps any StorageDriver and rewrites every namespace by prefixing with tenant + user identifiers.
Namespace transformation
Two tenants on the same underlying driver therefore see disjoint key spaces.
Methods
ScopedStorage implements the full StorageDriver interface — get, set, delete, list, initialize, close. Every call goes through the namespace rewrite. initialize() and close() delegate to the inner driver (so calling close() on the scoped wrapper closes the SHARED driver — be careful in production).
Use directly without a factory
If you don’t needAgentFactory’s sugar, just construct a fresh agent per request with a ScopedStorage:
AgentFactory
Sugar over ScopedStorage. Define the agent template once, call factory.create(scope) per request to materialize a scoped Agent.
Construction
AgentConfig you pass is the template. Don’t include userId or any per-user state in the template — that’s what the factory injects.
Per-request agent creation
factory.create({ ... }) returns a new Agent(...) whose:
userIdis set to the scoped user (used byuserMemoryand event tracing).memory.storageis wrapped inScopedStorage.checkpointing.storage,culture.storage,versioning.storageare also wrapped if present.register: falseis forced so the agent doesn’t pollute the globalregistry.
create() per request.
Method signature
create() with no args returns an unscoped Agent (useful for admin / cron jobs).
What gets scoped automatically
If you have custom tools that persist anything, wrap their storage in
ScopedStorage manually before adding them to the template.
TeamFactory
Identical pattern for Team:
memory.storage the same way. The team’s members are passed by reference; if they have their own scoped storage, that’s preserved.
WorkflowFactory
WorkflowFactory mostly disables global registration. The per-step agents (referenced by the workflow) carry whatever scope they were constructed with.
Putting it together — full SaaS pattern
Operational notes
- One storage driver per process. Multiple
ScopedStorageinstances share one underlying driver — keep the driver alive for the process lifetime. - Don’t close the inner driver via the scoped wrapper. Adding a check to prevent accidental close-via-wrap is on the roadmap; for now, just call
rawStorage.close()directly at shutdown. - For metrics / cost tracking, set
tenantIdonRunContext.metadataso observability picks it up. The factory already does this when you useuserId— extend totenantIdin your hooks. - Test isolation explicitly. A unit test that writes under tenant A and reads under tenant B (expecting null) catches regressions early.
See also
- Sessions and Memory — how sessions interact with scoped storage
- Incremental Session Manager — pair with
ScopedStoragefor cheap multi-tenant sessions - Cost Tracking — per-tenant budgets