π File detail
tools/AgentTool/agentMemory.ts
π― Use case
This module implements the βAgentToolβ tool (Agent) β something the model can call at runtime alongside other agent tools. On the API surface it exposes AgentMemoryScope, getAgentMemoryDir, isAgentMemoryPath, getAgentMemoryEntrypoint, and getMemoryScopeDisplay (and more) β mainly functions, hooks, or classes. Dependencies touch Node path helpers. It composes internal code from bootstrap, memdir, and utils (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import { join, normalize, sep } from 'path' import { getProjectRoot } from '../../bootstrap/state.js' import { buildMemoryPrompt, ensureMemoryDirExists,
π€ Exports (heuristic)
AgentMemoryScopegetAgentMemoryDirisAgentMemoryPathgetAgentMemoryEntrypointgetMemoryScopeDisplayloadAgentMemoryPrompt
π External import roots
Package roots from from "β¦" (relative paths omitted).
path
π₯οΈ Source preview
import { join, normalize, sep } from 'path'
import { getProjectRoot } from '../../bootstrap/state.js'
import {
buildMemoryPrompt,
ensureMemoryDirExists,
} from '../../memdir/memdir.js'
import { getMemoryBaseDir } from '../../memdir/paths.js'
import { getCwd } from '../../utils/cwd.js'
import { findCanonicalGitRoot } from '../../utils/git.js'
import { sanitizePath } from '../../utils/path.js'
// Persistent agent memory scope: 'user' (~/.claude/agent-memory/), 'project' (.claude/agent-memory/), or 'local' (.claude/agent-memory-local/)
export type AgentMemoryScope = 'user' | 'project' | 'local'
/**
* Sanitize an agent type name for use as a directory name.
* Replaces colons (invalid on Windows, used in plugin-namespaced agent
* types like "my-plugin:my-agent") with dashes.
*/
function sanitizeAgentTypeForPath(agentType: string): string {
return agentType.replace(/:/g, '-')
}
/**
* Returns the local agent memory directory, which is project-specific and not checked into VCS.
* When CLAUDE_CODE_REMOTE_MEMORY_DIR is set, persists to the mount with project namespacing.
* Otherwise, uses <cwd>/.claude/agent-memory-local/<agentType>/.
*/
function getLocalAgentMemoryDir(dirName: string): string {
if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) {
return (
join(
process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR,
'projects',
sanitizePath(
findCanonicalGitRoot(getProjectRoot()) ?? getProjectRoot(),
),
'agent-memory-local',
dirName,
) + sep
)
}
return join(getCwd(), '.claude', 'agent-memory-local', dirName) + sep
}
/**
* Returns the agent memory directory for a given agent type and scope.
* - 'user' scope: <memoryBase>/agent-memory/<agentType>/
* - 'project' scope: <cwd>/.claude/agent-memory/<agentType>/
* - 'local' scope: see getLocalAgentMemoryDir()
*/
export function getAgentMemoryDir(
agentType: string,
scope: AgentMemoryScope,
): string {
const dirName = sanitizeAgentTypeForPath(agentType)
switch (scope) {
case 'project':
return join(getCwd(), '.claude', 'agent-memory', dirName) + sep
case 'local':
return getLocalAgentMemoryDir(dirName)
case 'user':
return join(getMemoryBaseDir(), 'agent-memory', dirName) + sep
}
}
// Check if file is within an agent memory directory (any scope).
export function isAgentMemoryPath(absolutePath: string): boolean {
// SECURITY: Normalize to prevent path traversal bypasses via .. segments
const normalizedPath = normalize(absolutePath)
const memoryBase = getMemoryBaseDir()
// User scope: check memory base (may be custom dir or config home)
if (normalizedPath.startsWith(join(memoryBase, 'agent-memory') + sep)) {
return true
}
// Project scope: always cwd-based (not redirected)
if (
normalizedPath.startsWith(join(getCwd(), '.claude', 'agent-memory') + sep)
) {
return true
}
// Local scope: persisted to mount when CLAUDE_CODE_REMOTE_MEMORY_DIR is set, otherwise cwd-based
if (process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR) {
if (
normalizedPath.includes(sep + 'agent-memory-local' + sep) &&
normalizedPath.startsWith(
join(process.env.CLAUDE_CODE_REMOTE_MEMORY_DIR, 'projects') + sep,
)
) {
return true
}
} else if (
normalizedPath.startsWith(
join(getCwd(), '.claude', 'agent-memory-local') + sep,
)
) {
return true
}
return false
}
/**
* Returns the agent memory file path for a given agent type and scope.
*/
export function getAgentMemoryEntrypoint(
agentType: string,
scope: AgentMemoryScope,
): string {
return join(getAgentMemoryDir(agentType, scope), 'MEMORY.md')
}
export function getMemoryScopeDisplay(
memory: AgentMemoryScope | undefined,
): string {
switch (memory) {
case 'user':
return `User (${join(getMemoryBaseDir(), 'agent-memory')}/)`
case 'project':
return 'Project (.claude/agent-memory/)'
case 'local':
return `Local (${getLocalAgentMemoryDir('...')})`
default:
return 'None'
}
}
/**
* Load persistent memory for an agent with memory enabled.
* Creates the memory directory if needed and returns a prompt with memory contents.
*
* @param agentType The agent's type name (used as directory name)
* @param scope 'user' for ~/.claude/agent-memory/ or 'project' for .claude/agent-memory/
*/
export function loadAgentMemoryPrompt(
agentType: string,
scope: AgentMemoryScope,
): string {
let scopeNote: string
switch (scope) {
case 'user':
scopeNote =
'- Since this memory is user-scope, keep learnings general since they apply across all projects'
break
case 'project':
scopeNote =
'- Since this memory is project-scope and shared with your team via version control, tailor your memories to this project'
break
case 'local':
scopeNote =
'- Since this memory is local-scope (not checked into version control), tailor your memories to this project and machine'
break
}
const memoryDir = getAgentMemoryDir(agentType, scope)
// Fire-and-forget: this runs at agent-spawn time inside a sync
// getSystemPrompt() callback (called from React render in AgentDetail.tsx,
// so it cannot be async). The spawned agent won't try to Write until after
// a full API round-trip, by which time mkdir will have completed. Even if
// it hasn't, FileWriteTool does its own mkdir of the parent directory.
void ensureMemoryDirExists(memoryDir)
const coworkExtraGuidelines =
process.env.CLAUDE_COWORK_MEMORY_EXTRA_GUIDELINES
return buildMemoryPrompt({
displayName: 'Persistent Agent Memory',
memoryDir,
extraGuidelines:
coworkExtraGuidelines && coworkExtraGuidelines.trim().length > 0
? [scopeNote, coworkExtraGuidelines]
: [scopeNote],
})
}