How ai-memory Resolves Workspace and Project Scopes From the Current Working Directory

ai-memory determines the effective workspace and project by tracing a deterministic fallback chain from the agent's current working directory through an active-project pointer to configured default scopes.

ai-memory uses the agent's current working directory (cwd) as the anchor for automatic scope resolution. The system maps the cwd to concrete workspace and project identifiers through a shared ActiveProject structure, then applies server-wide defaults when no active entry exists. Key logic lives in crates/ai-memory-core/src/active_project.rs and crates/ai-memory-store/src/scope.rs, with cwd-based resolution behavior verified in crates/ai-memory-store/tests/session_scope_from_observations.rs.

Mapping the Current Working Directory to Workspace and Project Scopes

The resolution process begins when lifecycle-hook events carry the agent's cwd into the system.

Resolving the CWD to Workspace and Project IDs

The hook router inspects the current working directory and resolves it to a (workspace_id, project_id) pair. This lookup checks for an .ai-memory.toml marker file or infers the repository root. Once resolved, the pair is stored via ActiveProject::set_for in crates/ai-memory-core/src/active_project.rs.

The ActiveProject implementation maintains a per-actor or single-slot map depending on the configured ActiveProjectMode. The method writes the resolved ids into shared state so that subsequent tool calls can retrieve them without re-scanning the filesystem.

// Hook router populates the active project based on cwd.
let cwd = PathBuf::from("/home/alice/repos/my-app");
let (ws_id, proj_id) = resolve_cwd_to_ids(&cwd, &store.reader).await?;
active_project.set_for(&actor_key, ws_id, proj_id, false);

Resolving Workspace and Project Scopes for Read Operations

When tools perform read operations, ScopeResolver checks for explicit scope names before consulting the active-project pointer.

The resolve_current_or_project Fallback Chain

In crates/ai-memory-store/src/scope.rs, the ScopeResolver::resolve_read_args method delegates to resolve_current_or_project (lines 73-114). The method executes the following steps:

  1. If the caller supplied explicit workspace or project names, use them directly.
  2. Otherwise, call ActiveProject::get_for to retrieve the caller's active entry.
  3. If an active entry exists, search for the requested project inside that workspace.
  4. If the project is not found, fall back to the server's default workspace and its default project.
  5. If the project still cannot be found, return a ProjectNotFoundInActiveOrDefault error.
// Resolve a read request without explicit workspace/project.
let resolver = ScopeResolver::new(&store.reader, default_ws, default_proj)
    .with_active_project(&active_project);
let scope = resolver
    .resolve_read_args(None, Some("scratch"), &actor)
    .await?; // Falls back to active workspace → default workspace.

Default Workspace and Project Configuration

The server initializes a ScopeResolver with a default_workspace_id and default_project_id. These values are configured at startup—typically "default" for the workspace and "scratch" for the project—and are passed directly to ScopeResolver::new. They act as the final safety net whenever the active-project pointer is missing or insufficient.

Resolving Workspace and Project Scopes for Write Operations

Write operations follow similar fallback logic but include rules for automatic creation and explicit scope requirements.

How resolve_write_args Handles Missing Scopes

For write requests, ScopeResolver::resolve_write_args (lines 120-154 in crates/ai-memory-store/src/scope.rs) again uses the active-project pointer as a fallback. When the caller provides only a project name, the resolver creates that project inside the active workspace. If no active workspace exists, it falls back to the default workspace.

// Resolve a write request that only specifies a project name.
let resolver = ScopeResolver::new(&store.reader, default_ws, default_proj)
    .with_writer(&store.writer)
    .with_active_project(&active_project);
let new_scope = resolver
    .resolve_write_args(None, Some("new-feature"), &actor)
    .await?; // Creates "new-feature" under the active workspace.

Restrictions on Partial Explicit Scopes

The write path enforces strict validation for partial explicit scopes. If a caller supplies a project name without a matching workspace name in a context where both are required, the resolver rejects the request with a WorkspaceProjectPairRequired error. This prevents ambiguous scope creation.

The No-Creation Policy for Read-Only Paths

ai-memory enforces a strict separation between lookup and creation. All read-only paths—such as lookup_existing, resolve_many_existing, and the internal helpers lookup_existing_scope and lookup_existing_workspace—never create missing workspaces or projects. Only write-style helpers like create_explicit_scope and create_global_scope are permitted to insert new scope entries. This guarantees that read operations remain side-effect free.

Summary

  • The hook router resolves the agent's cwd to (workspace_id, project_id) and stores it in ActiveProject.
  • ScopeResolver in crates/ai-memory-store/src/scope.rs checks explicit arguments first, then the active-project pointer, then the default workspace and project.
  • Read operations use resolve_read_args and resolve_current_or_project; they never create scopes.
  • Write operations use resolve_write_args; they can create projects in the active or default workspace but reject partial explicit scopes with WorkspaceProjectPairRequired.
  • Configured defaults are initialized in ScopeResolver::new and serve as the final fallback.

Frequently Asked Questions

How does ai-memory determine the active workspace from the current directory?

The system inspects the agent's current working directory via lifecycle hooks, resolves it to workspace and project IDs by looking for .ai-memory.toml or inferring the repository root, and stores the result in the ActiveProject structure. Subsequent tool calls retrieve this pointer instead of scanning the filesystem again.

What happens if no explicit workspace or project is provided in a read request?

ScopeResolver::resolve_read_args falls back to the active-project pointer via resolve_current_or_project. If no active project exists, it uses the server's configured default workspace and project. If the project is still not found, it returns ProjectNotFoundInActiveOrDefault.

Can read-only tools create missing workspaces or projects automatically?

No. Read-only paths use helpers such as lookup_existing_scope and lookup_existing_workspace that are strictly lookup-only. Only write-style helpers like create_explicit_scope and create_global_scope can create new entries.

What error occurs when a write request provides only a project name without a workspace?

If the resolver requires an explicit pair but receives only a project name, it returns WorkspaceProjectPairRequired. However, if no explicit scope is provided at all, the write path falls back to the active workspace or the default workspace and creates the project there.

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 →