Skip to content

Commit 5b38060

Browse files
recuu-pfegclaude
andcommitted
feat: shared Agent Memory — write path + cross-agent injection
- collect_memory: parse shared/tags from memory frontmatter, pass to API - inject_memory: fetch shared memories matching task labels, inject as "Shared Team Knowledge" section alongside own memories - AgentMemory interface: add agent_name, shared, tags fields - putAgentMemory: accept shared and tags parameters Agents can now write shared: true in memory frontmatter to share knowledge across the team, with tag-based pinpoint injection. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fc235ff commit 5b38060

2 files changed

Lines changed: 45 additions & 8 deletions

File tree

src/agent-templates.ts

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,24 +1035,52 @@ ${outputFormat}`;
10351035
} catch { /* non-fatal */ }
10361036
}
10371037

1038-
// Inject agent memories
1038+
// Inject agent memories + shared memories
10391039
const memories = await ctx.api.fetchAgentMemories(ctx.agentName);
1040-
if (memories.length > 0) {
1040+
// Fetch shared memories matching task labels
1041+
const taskLabels: string[] = (() => {
1042+
const raw = (ctx.task as Record<string, unknown>).labels;
1043+
if (Array.isArray(raw)) return raw;
1044+
if (typeof raw === "string") { try { return JSON.parse(raw); } catch { return []; } }
1045+
return [];
1046+
})();
1047+
let sharedMemories: AgentMemory[] = [];
1048+
try {
1049+
const res = await fetch(`${ctx.config.apiUrl}/api/v1/agents/memories/shared${taskLabels.length ? `?tags=${taskLabels.join(",")}` : ""}`, {
1050+
headers: { Authorization: `Bearer ${ctx.config.apiKey}`, "Content-Type": "application/json" },
1051+
});
1052+
if (res.ok) {
1053+
const data = (await res.json()) as { memories: AgentMemory[] };
1054+
// Exclude own memories (already in `memories`)
1055+
const ownKeys = new Set(memories.map((m) => m.key));
1056+
sharedMemories = data.memories.filter((m) => !ownKeys.has(m.key));
1057+
}
1058+
} catch { /* non-fatal */ }
1059+
1060+
const allMemories = [...memories, ...sharedMemories];
1061+
if (allMemories.length > 0) {
1062+
const ownBlock = memories.length > 0
1063+
? memories.map((m) => `## ${m.type}: ${m.key}\n${m.content}`).join("\n\n")
1064+
: "";
1065+
const sharedBlock = sharedMemories.length > 0
1066+
? `\n# Shared Team Knowledge\n\n${sharedMemories.map((m) => `## ${m.type}: ${m.key} (from @${m.agent_name})\n${m.content}`).join("\n\n")}`
1067+
: "";
10411068
const memoryBlock = [
10421069
"<!-- TOBAN_MEMORY_START -->",
10431070
"# Agent Memory (auto-injected by Toban)",
10441071
"",
1045-
...memories.map((m) => `## ${m.type}: ${m.key}\n${m.content}`),
1072+
ownBlock,
1073+
sharedBlock,
10461074
"<!-- TOBAN_MEMORY_END -->",
1047-
].join("\n");
1075+
].filter(Boolean).join("\n");
10481076

10491077
let existing = fs.existsSync(claudeMdPath)
10501078
? fs.readFileSync(claudeMdPath, "utf-8")
10511079
: "";
10521080
// Remove existing memory block to prevent duplicates
10531081
existing = existing.replace(/<!-- TOBAN_MEMORY_START -->[\s\S]*?<!-- TOBAN_MEMORY_END -->\n?/g, "").trimEnd();
10541082
fs.writeFileSync(claudeMdPath, existing + "\n\n" + memoryBlock + "\n");
1055-
injected = memories.length;
1083+
injected = allMemories.length;
10561084
}
10571085

10581086
// Mark CLAUDE.md as assume-unchanged so inject_memory additions don't get committed
@@ -1113,7 +1141,13 @@ ${outputFormat}`;
11131141
const memType = getType[1].trim();
11141142
if (!["identity", "feedback", "project", "reference"].includes(memType)) continue;
11151143

1116-
await ctx.api.putAgentMemory(ctx.agentName, key, { type: memType, content: body });
1144+
// Parse optional shared and tags from frontmatter
1145+
const getShared = frontmatter.match(/^shared:\s*(true|false)$/m);
1146+
const getTags = frontmatter.match(/^tags:\s*(.+)$/m);
1147+
const shared = getShared?.[1] === "true";
1148+
const tags = getTags?.[1]?.trim() || undefined;
1149+
1150+
await ctx.api.putAgentMemory(ctx.agentName, key, { type: memType, content: body, shared, tags });
11171151
saved++;
11181152
} catch {
11191153
// Skip unparseable files

src/api-client.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ export interface AgentMemory {
7575
key: string;
7676
type: string;
7777
content: string;
78+
agent_name?: string;
79+
shared?: boolean;
80+
tags?: string;
7881
}
7982

8083
export interface ApiClient {
@@ -100,7 +103,7 @@ export interface ApiClient {
100103
fetchMySecrets(): Promise<Record<string, string>>;
101104
fetchApiDocs(agentName: string): Promise<string>;
102105
fetchAgentMemories(agentName: string): Promise<AgentMemory[]>;
103-
putAgentMemory(agentName: string, key: string, data: { type: string; content: string }): Promise<void>;
106+
putAgentMemory(agentName: string, key: string, data: { type: string; content: string; shared?: boolean; tags?: string }): Promise<void>;
104107
fetchRelevantFailures(): Promise<Array<{ summary: string; failure_type: string; agent_name: string | null; created_at: string }>>;
105108
recordFailure(data: { task_id: string; failure_type: string; summary: string; agent_name?: string; sprint?: number; review_comment?: string; files_involved?: string }): Promise<void>;
106109
}
@@ -313,7 +316,7 @@ export function createApiClient(apiUrl: string, apiKey: string): ApiClient {
313316
}
314317
},
315318

316-
async putAgentMemory(agentName: string, key: string, data: { type: string; content: string }): Promise<void> {
319+
async putAgentMemory(agentName: string, key: string, data: { type: string; content: string; shared?: boolean; tags?: string }): Promise<void> {
317320
try {
318321
await fetch(`${apiUrl}/api/v1/agents/${encodeURIComponent(agentName)}/memories/${encodeURIComponent(key)}`, {
319322
method: "PUT",

0 commit comments

Comments
 (0)