How to Resolve a Project Scope from Caller Coordinates in ai-memory
Call the resolve_read_args or resolve_write_args methods on a ScopeResolver instance to map caller-supplied workspace and project names to internal WorkspaceId and ProjectId pairs, using the resolution logic implemented in crates/ai-memory-store/src/scope.rs.
The ai-memory system identifies resources through internal ID pairs, but callers typically provide human-readable coordinates like workspace and project names. Understanding how to resolve a project scope from caller coordinates is essential when building MCP servers, HTTP handlers, or CLI tools that interact with the ai-memory store.
Understanding the ScopeResolver Architecture
The ScopeResolver type acts as the central authority for translating caller coordinates into internal identifiers. Located in crates/ai-memory-store/src/scope.rs, this struct encapsulates all resolution logic and maintains references to the storage backend and default configurations.
Internal IDs vs Caller Coordinates
Project scopes in ai-memory are identified by two integers: WorkspaceId and ProjectId. However, external callers (MCP clients, HTTP requests, or CLI commands) supply coordinates—optional string names for the workspace and project they want to access. The resolver bridges this gap by querying the SQLite store and applying fallback rules when coordinates are incomplete.
Core Components
The resolver relies on three primary dependencies:
ReaderPool– Provides async read access to the SQLite store for looking up existing workspaces and projectsWriterHandle– Enables create-on-write behavior for resolving scopes during mutationsActiveProject– Tracks per-actor "current project" state, allowing callers to omit explicit coordinates when operating within an established context
Building a ScopeResolver Instance
Before resolving any coordinates, you must construct the resolver with appropriate defaults and optional helper attachments.
Constructor and Defaults
Initialize the resolver using ScopeResolver::new(), passing a read-only ReaderPool and the default workspace/project IDs that the server should use when callers supply no explicit coordinates:
use ai_memory_store::{ScopeResolver, ReaderPool};
let resolver = ScopeResolver::new(
reader_pool,
default_workspace_id,
default_project_id
);
According to the source code at lines 24-31 in crates/ai-memory-store/src/scope.rs, this constructor stores the reader and defaults within the struct, preparing the resolver for subsequent resolution calls.
Attaching Optional Helpers
For advanced use cases, chain builder methods to attach write capabilities and active-project tracking:
with_writer(writer)– Attaches aWriterHandlefor create-on-write resolutionwith_active_project(active_project)– Attaches anActiveProjectmap for current-project fallbacks
let resolver = ScopeResolver::new(reader, default_ws, default_proj)
.with_writer(writer_handle)
.with_active_project(active_project_map);
These methods, found at lines 41-53 in scope.rs, simply set optional fields on the resolver struct.
Resolving Read Operations
For read-only operations, use resolve_read_args to convert caller coordinates into validated internal IDs without modifying the store.
The resolve_read_args Method
This method accepts optional workspace and project names plus an ActorKey identifying the caller. It follows a strict resolution hierarchy defined at lines 68-82 in scope.rs:
- If both workspace and project names are provided, resolve them directly
- If only one is provided, return a
WorkspaceProjectPairRequirederror - If neither is provided, delegate to
resolve_current_or_projectfor fallback logic
let scope = resolver
.resolve_read_args(
optional_workspace_name,
optional_project_name,
&actor_key
)
.await?;
// Returns ResolvedScope { workspace_id, project_id }
Current-Project Fallback Logic
When callers supply no explicit coordinates, resolve_current_or_project (lines 84-115) attempts three fallbacks in order:
- The caller's active-project entry from the
ActiveProjectmap - The default workspace's project
- The server-wide default workspace/project pair
If the caller provides a project name without a workspace, the resolver prefers the active workspace's project; otherwise, it returns the active project or defaults directly.
Handling Write Operations
Write paths require resolve_write_args, which behaves like the read-side counterpart but can create missing workspaces or projects on demand.
Create-on-Write Behavior
When resolve_write_args receives a project name without explicit coordinates, it first attempts the active-project fallback. If a project name is supplied, the method requires an attached writer handle and calls writer.get_or_create_workspace() and writer.get_or_create_project() to ensure the entities exist before returning their IDs.
This logic spans lines 33-86 in scope.rs and handles the "ambiguous unscoped write" case by either using the active project or creating new entities based on the writer configuration.
For batch operations, resolve_many_existing deduplicates a list of explicit (workspace, project) pairs without creating anything, delegating to resolve_many_existing_scopes at lines 86-93.
Integration Examples
The resolver appears throughout the ai-memory codebase, demonstrating consistent patterns for both server and CLI contexts.
MCP Server Usage
In crates/ai-memory-mcp/src/server.rs (lines 1244-1245), the MCP server builds a resolver once per request and exposes it via a scope_resolver() method. This pattern ensures that each incoming MCP request gets a fresh resolver instance with the current actor's context and server defaults.
CLI Serve Command
The CLI's HTTP server follows the same architecture in crates/ai-memory-cli/src/commands/serve.rs. When handling incoming HTTP calls, the command constructs a ScopeResolver with the configured defaults and attaches the appropriate reader and writer pools to handle both read and write operations from API clients.
Summary
- ScopeResolver in
crates/ai-memory-store/src/scope.rshandles all coordinate-to-ID translation - Build resolvers with
ScopeResolver::new()and chainwith_writer()orwith_active_project()as needed - Use
resolve_read_args()for queries; it errors withWorkspaceProjectPairRequiredif coordinates are incomplete - Use
resolve_write_args()for mutations; it creates missing workspaces/projects via an attachedWriterHandle - Leverage
ActiveProjectmaps to support "current project" semantics for CLI and interactive contexts - Both the MCP server and CLI serve command instantiate per-request resolvers following identical patterns
Frequently Asked Questions
What happens if I provide only a workspace name without a project name?
The resolver returns a WorkspaceProjectPairRequired error. According to the implementation in resolve_read_args, ai-memory requires either both coordinates (workspace and project) or neither, relying on the fallback chain to determine the scope. Partial coordinates are considered ambiguous and are rejected.
How does the active project map work in scope resolution?
The ActiveProject map tracks the current project for each ActorKey. When no explicit coordinates are provided, resolve_current_or_project queries this map first. If the caller has established a current project in previous interactions, that scope is used. This enables CLI workflows where users "cd" into a project and subsequent commands operate implicitly within that context.
Can I resolve multiple scopes at once?
Yes. Use resolve_many_existing, which accepts a list of (workspace, project) tuples and deduplicates them without creating new entities. This method delegates to resolve_many_existing_scopes and is useful for batch validation operations where you need to verify that multiple referenced scopes exist in the database.
What is the difference between resolve_read_args and resolve_write_args?
resolve_read_args strictly validates existing scopes and never modifies the database, making it suitable for queries. resolve_write_args handles mutation contexts: it can create workspaces and projects on demand using an attached WriterHandle, and it follows slightly different fallback rules to handle "unscoped writes" that should target the active project or create new entities.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →