How Project Scope Is Determined in ai-memory: The ScopeResolver Mechanism

ai-memory determines project scope through the ScopeResolver struct in crates/ai-memory-store/src/scope.rs, which isolates data by workspace and project using a combination of explicit parameters, active project detection via .ai-memory.toml markers, and fallback defaults.

Understanding how project scope is determined in ai-memory is essential for managing isolated data environments across collaborative AI workflows. The akitaonrails/ai-memory repository implements a strict isolation mechanism centered around the ScopeResolver type, which guarantees that every operation executes within the correct workspace and project boundary defined by the user, CLI context, or explicit API parameters.

The ScopeResolver Core Architecture

Every request initializes a ScopeResolver by calling ScopeResolver::new with a borrowed ReaderPool and the default workspace and project IDs. According to the source code at scope.rs:L167-L173, this constructor establishes the baseline isolation context, typically referencing the "current-project" defaults.

The resolver supports two optional helper attachments configured via builder methods:

  • with_writer – Attaches a WriterHandle for requests that may create new scopes during write operations
  • with_active_project – Attaches the ActiveProject map when the request should honor the current directory's project context

These attachments are implemented at scope.rs:L30-L42.

Resolving Read Operations

The resolve_read_args method determines which workspace and project to use for read queries. Located at scope.rs:L357-L376, this function implements a strict lookup-only policy:

  1. Explicit parameters – If both workspace and project are supplied, the resolver calls lookup_existing_scope without creating any new entries
  2. Partial parameters – If only one is supplied, the missing value defaults to the active project (if attached) or the default IDs
  3. No parameters – Falls back entirely to the default workspace and project established during resolver construction

Resolving Write Operations

Write operations follow a similar resolution path via resolve_write_args at scope.rs:L382-L401, with one critical distinction: this method can create missing scopes. When a write request targets a non-existent workspace or project, the resolver invokes create_explicit_scope to insert the new entities into the underlying SQLite store before proceeding with the operation.

Auto-Scope Detection via .ai-memory.toml

The auto-scope mechanism enables automatic project detection without explicit parameters. As documented in docs/auto-scope.md and implemented in crates/ai-memory-core/src/routing_snippet.rs, the system searches the current directory and its ancestors for a .ai-memory.toml marker file. The ActiveProject map extracts workspace and project names from this file, allowing the CLI to default to the "current-project" context when launched inside a recognized project directory. If no marker is found, the system falls back to the scratch workspace defined at startup.

Global Scope Handling

ai-memory defines a special global scope via the constant GLOBAL_SCOPE_PROJECT within the default workspace. The lookup_global_scope and create_global_scope functions at scope.rs:L35-L63 manage this shared context. Read operations automatically union results from the global scope unless the caller explicitly sets global=false. Write operations targeting the global scope use the dedicated creation path to ensure the shared project exists.

Multi-Scope Resolution

For operations spanning multiple contexts, the resolve_many_existing_scopes function at scope.rs:L83-L102 handles de-duplication and enforces a hard limit on the number of scopes processed in a single request. This prevents resource exhaustion during bulk operations by validating the scope list against existing records before execution.

Server Integration

The MCP/HTTP server instantiates the resolver once per request via the scope_resolver() helper at server.rs:L1233-L1235. It feeds the resolver workspace and project IDs extracted from URL path parameters or the request's active-project context, ensuring consistent isolation across API boundaries.

Practical Implementation Examples

// Resolve a read request inside a running CLI session
let resolver = ScopeResolver::new(&reader_pool, default_ws, default_prj)
    .with_active_project(&active_project);
let resolved = resolver
    .resolve_read_args(Some("my_ws"), None, &actor_key)
    .await?;               // → uses "my_ws" and the active project as defaults
// Resolve a write request that may create a new project
let resolver = ScopeResolver::new(&reader_pool, default_ws, default_prj)
    .with_writer(&writer_handle);
let resolved = resolver
    .resolve_write_args(Some("new_ws"), Some("new_prj"), &actor_key)
    .await?;               // → creates “new_ws/new_prj” if they do not exist
// Direct lookup for admin routes that must not create anything
let existing = lookup_existing_scope(&reader_pool, "existing_ws", "existing_prj")
    .await?;               // Returns ResolvedScope or an error if missing
// Resolving many scopes for a bulk operation
let scopes = vec![
    ScopeName { workspace: "ws1".into(), project: "prjA".into() },
    ScopeName { workspace: "ws2".into(), project: "prjB".into() },
];
let resolved = resolve_many_existing_scopes(&reader_pool, &scopes, 10).await?;

Summary

  • ScopeResolver is the single source of truth for project scope determination, instantiated per-request with default workspace and project IDs
  • Read operations strictly lookup existing scopes via resolve_read_args, while write operations can create missing scopes via resolve_write_args and create_explicit_scope
  • Auto-scope detection relies on the .ai-memory.toml marker file and the ActiveProject map to automatically determine context from the current working directory
  • Global scope is automatically included in reads unless explicitly disabled, providing a shared context across all workspaces
  • Multi-scope operations are validated and de-duplicated through resolve_many_existing_scopes to enforce limits and prevent conflicts

Frequently Asked Questions

What happens if I don't specify a workspace or project in ai-memory?

The resolver falls back to the default workspace and project IDs passed during ScopeResolver::new construction. In CLI contexts, these defaults correspond to the "current project" detected via the .ai-memory.toml marker file or the scratch workspace defined at startup if no marker exists.

How does ai-memory automatically detect the current project?

When with_active_project is called on the resolver, it activates the auto-scope mechanism. This searches the current directory and its ancestors for a .ai-memory.toml file, extracting workspace and project names to use as defaults. If the file is not found, the system relies on explicit parameters or fallback defaults.

Can I write to multiple scopes at once in ai-memory?

Yes, using resolve_many_existing_scopes, which accepts a vector of ScopeName structs. This function de-duplicates the list and enforces a hard limit on the number of scopes to prevent resource exhaustion, but note that it only resolves existing scopes and does not create new ones during the bulk lookup.

What is the global scope in ai-memory and how is it accessed?

The global scope is a special project (GLOBAL_SCOPE_PROJECT) within the default workspace that provides shared data across all contexts. It is automatically unioned with specific project results during read operations unless global=false is explicitly set. Writes to the global scope use dedicated create_global_scope and lookup_global_scope functions to ensure the shared project exists.

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 →