Skip to content

Commit de81093

Browse files
author
Ubuntu
committed
Add agent-memory provider
Adds support for benchmarking agent-memory (github.com/g1itchbot8888-del/agent-memory): - HTTP bench server integration (port 9876) - Semantic search with graph-enhanced retrieval - Memory relations (extends, contradicts, supersedes) - LearningMachine for meta-memory Run with: bun run benchmark --provider agent-memory Requires agent-memory bench server running locally.
1 parent bcfe4c4 commit de81093

5 files changed

Lines changed: 171 additions & 2 deletions

File tree

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import type {
2+
Provider,
3+
ProviderConfig,
4+
IngestOptions,
5+
IngestResult,
6+
SearchOptions,
7+
IndexingProgressCallback,
8+
} from "../../types/provider"
9+
import type { UnifiedSession } from "../../types/unified"
10+
import { logger } from "../../utils/logger"
11+
import { AGENT_MEMORY_PROMPTS } from "./prompts"
12+
13+
/**
14+
* agent-memory provider for MemoryBench.
15+
*
16+
* Connects to a local agent-memory bench server (Python HTTP wrapper).
17+
* The server manages per-container SQLite databases with semantic embeddings
18+
* and graph-based memory relationships.
19+
*
20+
* Start the server: python -m agent_memory.bench_server --port 9876
21+
*/
22+
export class AgentMemoryProvider implements Provider {
23+
name = "agent-memory"
24+
prompts = AGENT_MEMORY_PROMPTS
25+
concurrency = {
26+
default: 10, // Local, so moderate concurrency
27+
}
28+
private baseUrl: string = "http://127.0.0.1:9876"
29+
30+
async initialize(config: ProviderConfig): Promise<void> {
31+
if (config.baseUrl) {
32+
this.baseUrl = config.baseUrl
33+
}
34+
35+
// Health check
36+
try {
37+
const res = await fetch(`${this.baseUrl}/health`)
38+
if (!res.ok) throw new Error(`HTTP ${res.status}`)
39+
const data = await res.json() as { status: string }
40+
logger.info(`Connected to agent-memory bench server: ${data.status}`)
41+
} catch (e) {
42+
throw new Error(
43+
`Cannot connect to agent-memory bench server at ${this.baseUrl}. ` +
44+
`Start it with: python -m agent_memory.bench_server --port 9876\n` +
45+
`Error: ${e}`
46+
)
47+
}
48+
}
49+
50+
async ingest(sessions: UnifiedSession[], options: IngestOptions): Promise<IngestResult> {
51+
// Send sessions in batches to avoid overwhelming the server
52+
const batchSize = 5
53+
const allDocIds: string[] = []
54+
55+
for (let i = 0; i < sessions.length; i += batchSize) {
56+
const batch = sessions.slice(i, i + batchSize)
57+
58+
const res = await fetch(`${this.baseUrl}/ingest`, {
59+
method: "POST",
60+
headers: { "Content-Type": "application/json" },
61+
body: JSON.stringify({
62+
containerTag: options.containerTag,
63+
sessions: batch,
64+
}),
65+
})
66+
67+
if (!res.ok) {
68+
const text = await res.text()
69+
throw new Error(`Ingest failed: ${text}`)
70+
}
71+
72+
const data = await res.json() as { documentIds: string[], count: number }
73+
allDocIds.push(...data.documentIds)
74+
75+
if (i % 20 === 0 && i > 0) {
76+
logger.info(`Ingested ${i}/${sessions.length} sessions (${allDocIds.length} memories)`)
77+
}
78+
}
79+
80+
logger.info(`Ingested ${sessions.length} sessions → ${allDocIds.length} memories`)
81+
return { documentIds: allDocIds }
82+
}
83+
84+
async awaitIndexing(
85+
result: IngestResult,
86+
_containerTag: string,
87+
onProgress?: IndexingProgressCallback
88+
): Promise<void> {
89+
// agent-memory indexes synchronously on ingest, no waiting needed
90+
const total = result.documentIds.length
91+
onProgress?.({
92+
completedIds: result.documentIds,
93+
failedIds: [],
94+
total,
95+
})
96+
}
97+
98+
async search(query: string, options: SearchOptions): Promise<unknown[]> {
99+
const res = await fetch(`${this.baseUrl}/search`, {
100+
method: "POST",
101+
headers: { "Content-Type": "application/json" },
102+
body: JSON.stringify({
103+
containerTag: options.containerTag,
104+
query,
105+
limit: options.limit || 30,
106+
}),
107+
})
108+
109+
if (!res.ok) {
110+
const text = await res.text()
111+
throw new Error(`Search failed: ${text}`)
112+
}
113+
114+
const data = await res.json() as { results: unknown[] }
115+
return data.results ?? []
116+
}
117+
118+
async clear(containerTag: string): Promise<void> {
119+
const res = await fetch(`${this.baseUrl}/clear`, {
120+
method: "POST",
121+
headers: { "Content-Type": "application/json" },
122+
body: JSON.stringify({ containerTag }),
123+
})
124+
125+
if (!res.ok) {
126+
logger.warn(`Clear failed for ${containerTag}`)
127+
} else {
128+
logger.info(`Cleared memories for: ${containerTag}`)
129+
}
130+
}
131+
}
132+
133+
export default AgentMemoryProvider
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import type { ProviderPrompts } from "../../types/prompts"
2+
3+
export const AGENT_MEMORY_PROMPTS: ProviderPrompts = {
4+
answerPrompt: (question: string, context: unknown[], questionDate?: string) => {
5+
const memories = (context as Array<{ memory?: string; score?: number }>)
6+
.map((r, i) => {
7+
const memory = r.memory || JSON.stringify(r)
8+
const score = r.score ? ` (relevance: ${r.score.toFixed(2)})` : ""
9+
return `${i + 1}. ${memory}${score}`
10+
})
11+
.join("\n")
12+
13+
return `You are answering questions based on memories from past conversations.
14+
15+
${questionDate ? `Current date context: ${questionDate}\n` : ""}
16+
Retrieved memories:
17+
${memories || "(no relevant memories found)"}
18+
19+
Question: ${question}
20+
21+
Instructions:
22+
- Answer ONLY based on the retrieved memories above
23+
- If the memories don't contain enough information, say so
24+
- Be specific and cite details from the memories
25+
- For temporal questions, pay attention to dates and sequence of events
26+
- If memories contradict each other, prefer the most recent one
27+
28+
Answer:`
29+
},
30+
}

src/providers/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import type { ConcurrencyConfig } from "../types/concurrency"
33
import { SupermemoryProvider } from "./supermemory"
44
import { Mem0Provider } from "./mem0"
55
import { ZepProvider } from "./zep"
6+
import { AgentMemoryProvider } from "./agent-memory"
67

78
const providers: Record<ProviderName, new () => Provider> = {
89
supermemory: SupermemoryProvider,
910
mem0: Mem0Provider,
1011
zep: ZepProvider,
12+
"agent-memory": AgentMemoryProvider,
1113
}
1214

1315
export function createProvider(name: ProviderName): Provider {
@@ -35,4 +37,4 @@ export function getProviderInfo(name: ProviderName): {
3537
}
3638
}
3739

38-
export { SupermemoryProvider, Mem0Provider, ZepProvider }
40+
export { SupermemoryProvider, Mem0Provider, ZepProvider, AgentMemoryProvider }

src/types/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,4 @@ export interface Provider {
4747
clear(containerTag: string): Promise<void>
4848
}
4949

50-
export type ProviderName = "supermemory" | "mem0" | "zep"
50+
export type ProviderName = "supermemory" | "mem0" | "zep" | "agent-memory"

src/utils/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export interface Config {
33
supermemoryBaseUrl: string
44
mem0ApiKey: string
55
zepApiKey: string
6+
agentMemoryBaseUrl: string
67
openaiApiKey: string
78
anthropicApiKey: string
89
googleApiKey: string
@@ -13,6 +14,7 @@ export const config: Config = {
1314
supermemoryBaseUrl: process.env.SUPERMEMORY_BASE_URL || "https://api.supermemory.ai",
1415
mem0ApiKey: process.env.MEM0_API_KEY || "",
1516
zepApiKey: process.env.ZEP_API_KEY || "",
17+
agentMemoryBaseUrl: process.env.AGENT_MEMORY_BASE_URL || "http://127.0.0.1:9876",
1618
openaiApiKey: process.env.OPENAI_API_KEY || "",
1719
anthropicApiKey: process.env.ANTHROPIC_API_KEY || "",
1820
googleApiKey: process.env.GOOGLE_API_KEY || "",
@@ -26,6 +28,8 @@ export function getProviderConfig(provider: string): { apiKey: string; baseUrl?:
2628
return { apiKey: config.mem0ApiKey }
2729
case "zep":
2830
return { apiKey: config.zepApiKey }
31+
case "agent-memory":
32+
return { apiKey: "local", baseUrl: config.agentMemoryBaseUrl }
2933
default:
3034
throw new Error(`Unknown provider: ${provider}`)
3135
}

0 commit comments

Comments
 (0)