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 projects
  • WriterHandle – Enables create-on-write behavior for resolving scopes during mutations
  • ActiveProject – 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 a WriterHandle for create-on-write resolution
  • with_active_project(active_project) – Attaches an ActiveProject map 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:

  1. If both workspace and project names are provided, resolve them directly
  2. If only one is provided, return a WorkspaceProjectPairRequired error
  3. If neither is provided, delegate to resolve_current_or_project for 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:

  1. The caller's active-project entry from the ActiveProject map
  2. The default workspace's project
  3. 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.rs handles all coordinate-to-ID translation
  • Build resolvers with ScopeResolver::new() and chain with_writer() or with_active_project() as needed
  • Use resolve_read_args() for queries; it errors with WorkspaceProjectPairRequired if coordinates are incomplete
  • Use resolve_write_args() for mutations; it creates missing workspaces/projects via an attached WriterHandle
  • Leverage ActiveProject maps 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →