Skip to content

Commit 3fe2240

Browse files
authored
Add layered memory capture APIs and demos (#5)
* Add V0 memory layer * Extend memory tooling and docs * Add layered memory capture APIs and demos * Fix automatic memory capture CI test
1 parent 2c41a0f commit 3fe2240

19 files changed

Lines changed: 4686 additions & 9 deletions

DemoApp/AssistantRuntimeDemoApp.xcodeproj/project.pbxproj

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
1A2B3C4D5E6F70000000000D /* DemoUIComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F70000000000D /* DemoUIComponents.swift */; };
2323
1A2B3C4D5E6F70000000000E /* StructuredOutputDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F70000000000E /* StructuredOutputDemoView.swift */; };
2424
1A2B3C4D5E6F70000000000F /* ThreadDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F70000000000F /* ThreadDetailView.swift */; };
25+
1A2B3C4D5E6F700000000010 /* DemoMemoryExamples.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F700000000010 /* DemoMemoryExamples.swift */; };
26+
1A2B3C4D5E6F700000000011 /* AgentDemoViewModel+Memory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F700000000011 /* AgentDemoViewModel+Memory.swift */; };
27+
1A2B3C4D5E6F700000000012 /* MemoryDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A2B3C4D5E6F700000000012 /* MemoryDemoView.swift */; };
2528
7482123BC63AC10F104DE092 /* AssistantRuntimeDemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5A6999E6475919476E726E8C /* AssistantRuntimeDemoApp.swift */; };
2629
84726927B752451499D9257F /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 906A95007C8ECB92CFC2CE15 /* Foundation.framework */; };
2730
B060448C6464C41789B56EED /* AgentDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA22585116A120BA97F76B8 /* AgentDemoView.swift */; };
@@ -47,6 +50,9 @@
4750
2A2B3C4D5E6F70000000000D /* DemoUIComponents.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DemoUIComponents.swift; sourceTree = "<group>"; };
4851
2A2B3C4D5E6F70000000000E /* StructuredOutputDemoView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = StructuredOutputDemoView.swift; sourceTree = "<group>"; };
4952
2A2B3C4D5E6F70000000000F /* ThreadDetailView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = ThreadDetailView.swift; sourceTree = "<group>"; };
53+
2A2B3C4D5E6F700000000010 /* DemoMemoryExamples.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = DemoMemoryExamples.swift; sourceTree = "<group>"; };
54+
2A2B3C4D5E6F700000000011 /* AgentDemoViewModel+Memory.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "AgentDemoViewModel+Memory.swift"; sourceTree = "<group>"; };
55+
2A2B3C4D5E6F700000000012 /* MemoryDemoView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = MemoryDemoView.swift; sourceTree = "<group>"; };
5056
2481147A958D00EB4A70C928 /* AgentDemoViewModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AgentDemoViewModel.swift; sourceTree = "<group>"; };
5157
3CA22585116A120BA97F76B8 /* AgentDemoView.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AgentDemoView.swift; sourceTree = "<group>"; };
5258
5A6999E6475919476E726E8C /* AssistantRuntimeDemoApp.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = AssistantRuntimeDemoApp.swift; sourceTree = "<group>"; };
@@ -137,6 +143,9 @@
137143
2A2B3C4D5E6F70000000000D /* DemoUIComponents.swift */,
138144
2A2B3C4D5E6F70000000000E /* StructuredOutputDemoView.swift */,
139145
2A2B3C4D5E6F70000000000F /* ThreadDetailView.swift */,
146+
2A2B3C4D5E6F700000000010 /* DemoMemoryExamples.swift */,
147+
2A2B3C4D5E6F700000000011 /* AgentDemoViewModel+Memory.swift */,
148+
2A2B3C4D5E6F700000000012 /* MemoryDemoView.swift */,
140149
);
141150
name = Shared;
142151
path = Shared;
@@ -228,6 +237,9 @@
228237
1A2B3C4D5E6F70000000000D /* DemoUIComponents.swift in Sources */,
229238
1A2B3C4D5E6F70000000000E /* StructuredOutputDemoView.swift in Sources */,
230239
1A2B3C4D5E6F70000000000F /* ThreadDetailView.swift in Sources */,
240+
1A2B3C4D5E6F700000000010 /* DemoMemoryExamples.swift in Sources */,
241+
1A2B3C4D5E6F700000000011 /* AgentDemoViewModel+Memory.swift in Sources */,
242+
1A2B3C4D5E6F700000000012 /* MemoryDemoView.swift in Sources */,
231243
7482123BC63AC10F104DE092 /* AssistantRuntimeDemoApp.swift in Sources */,
232244
BB4F38E64D1EBBB3821AC4E3 /* AgentDemoRuntimeFactory.swift in Sources */,
233245
B060448C6464C41789B56EED /* AgentDemoView.swift in Sources */,

DemoApp/AssistantRuntimeDemoApp/AssistantRuntimeDemoApp.swift

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import SwiftUI
33
enum DemoTab: Hashable {
44
case assistant
55
case structuredOutput
6+
case memory
67
case healthCoach
78
}
89

@@ -45,6 +46,19 @@ struct AssistantRuntimeDemoApp: App {
4546
Label("Structured", systemImage: "square.stack.3d.up")
4647
}
4748

49+
NavigationStack {
50+
MemoryDemoView(
51+
viewModel: viewModel,
52+
selectedTab: $selectedTab
53+
)
54+
.navigationTitle("Memory")
55+
.navigationBarTitleDisplayMode(.inline)
56+
}
57+
.tag(DemoTab.memory)
58+
.tabItem {
59+
Label("Memory", systemImage: "brain")
60+
}
61+
4862
NavigationStack {
4963
HealthCoachView(viewModel: viewModel)
5064
.navigationTitle("Health Coach")

DemoApp/AssistantRuntimeDemoApp/Shared/AgentDemoRuntimeFactory.swift

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,21 @@ enum AgentDemoRuntimeFactory {
102102
)
103103
),
104104
approvalPresenter: approvalInbox,
105-
stateStore: FileRuntimeStateStore(url: stateURL ?? defaultStateURL())
105+
stateStore: FileRuntimeStateStore(url: stateURL ?? defaultStateURL()),
106+
memory: .init(
107+
store: try! SQLiteMemoryStore(url: defaultMemoryURL()),
108+
automaticCapturePolicy: .init(
109+
source: .lastTurn,
110+
options: .init(
111+
defaults: .init(
112+
namespace: DemoMemoryExamples.namespace,
113+
kind: "preference",
114+
tags: ["demo", "auto-capture"]
115+
),
116+
maxMemories: 2
117+
)
118+
)
119+
)
106120
))
107121
}
108122
#endif
@@ -129,7 +143,21 @@ enum AgentDemoRuntimeFactory {
129143
)
130144
),
131145
approvalPresenter: NonInteractiveApprovalPresenter(),
132-
stateStore: FileRuntimeStateStore(url: defaultStateURL())
146+
stateStore: FileRuntimeStateStore(url: defaultStateURL()),
147+
memory: .init(
148+
store: try! SQLiteMemoryStore(url: defaultMemoryURL()),
149+
automaticCapturePolicy: .init(
150+
source: .lastTurn,
151+
options: .init(
152+
defaults: .init(
153+
namespace: DemoMemoryExamples.namespace,
154+
kind: "preference",
155+
tags: ["demo", "auto-capture"]
156+
),
157+
maxMemories: 2
158+
)
159+
)
160+
)
133161
))
134162
}
135163

@@ -143,6 +171,17 @@ enum AgentDemoRuntimeFactory {
143171
.appendingPathComponent("AssistantRuntimeDemoApp", isDirectory: true)
144172
.appendingPathComponent("runtime-state.json")
145173
}
174+
175+
static func defaultMemoryURL() -> URL {
176+
let baseDirectory = FileManager.default.urls(
177+
for: .applicationSupportDirectory,
178+
in: .userDomainMask
179+
).first ?? URL(fileURLWithPath: NSTemporaryDirectory())
180+
181+
return baseDirectory
182+
.appendingPathComponent("AssistantRuntimeDemoApp", isDirectory: true)
183+
.appendingPathComponent("memory.sqlite")
184+
}
146185
}
147186

148187
private struct NonInteractiveApprovalPresenter: ApprovalPresenting {
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import CodexKit
2+
import Foundation
3+
4+
@MainActor
5+
extension AgentDemoViewModel {
6+
func runAutomaticPolicyMemoryDemo() async {
7+
guard session != nil else {
8+
lastError = "Sign in before running policy-based memory capture."
9+
return
10+
}
11+
guard !isRunningMemoryDemo else {
12+
return
13+
}
14+
15+
isRunningMemoryDemo = true
16+
lastError = nil
17+
defer {
18+
isRunningMemoryDemo = false
19+
}
20+
21+
do {
22+
let thread = try await runtime.createThread(
23+
title: "Memory Demo: Automatic Policy",
24+
memoryContext: AgentMemoryContext(
25+
namespace: DemoMemoryExamples.namespace,
26+
scopes: [DemoMemoryExamples.healthCoachScope],
27+
kinds: ["preference"]
28+
)
29+
)
30+
_ = try await runtime.sendMessage(
31+
UserMessageRequest(text: DemoMemoryExamples.automaticPolicyPrompt),
32+
in: thread.id
33+
)
34+
35+
let store = try SQLiteMemoryStore(url: AgentDemoRuntimeFactory.defaultMemoryURL())
36+
let result = try await store.query(
37+
MemoryQuery(
38+
namespace: DemoMemoryExamples.namespace,
39+
scopes: [DemoMemoryExamples.healthCoachScope],
40+
text: "direct blunt steps",
41+
limit: 4,
42+
maxCharacters: 800
43+
)
44+
)
45+
46+
automaticPolicyMemoryResult = AutomaticPolicyMemoryDemoResult(
47+
threadID: thread.id,
48+
threadTitle: thread.title ?? "Memory Demo: Automatic Policy",
49+
prompt: DemoMemoryExamples.automaticPolicyPrompt,
50+
records: result.matches.map(\.record)
51+
)
52+
threads = await runtime.threads()
53+
} catch {
54+
lastError = error.localizedDescription
55+
}
56+
}
57+
58+
func runAutomaticMemoryDemo() async {
59+
guard session != nil else {
60+
lastError = "Sign in before running automatic memory capture."
61+
return
62+
}
63+
guard !isRunningMemoryDemo else {
64+
return
65+
}
66+
67+
isRunningMemoryDemo = true
68+
lastError = nil
69+
defer {
70+
isRunningMemoryDemo = false
71+
}
72+
73+
do {
74+
let thread = try await runtime.createThread(
75+
title: "Memory Demo: Automatic Capture",
76+
memoryContext: DemoMemoryExamples.previewContext
77+
)
78+
let capture = try await runtime.captureMemories(
79+
from: .text(DemoMemoryExamples.automaticCaptureTranscript),
80+
for: thread.id,
81+
options: .init(
82+
defaults: DemoMemoryExamples.guidedDefaults,
83+
maxMemories: 3
84+
)
85+
)
86+
automaticMemoryResult = AutomaticMemoryDemoResult(
87+
threadID: thread.id,
88+
threadTitle: thread.title ?? "Memory Demo: Automatic Capture",
89+
capture: capture
90+
)
91+
threads = await runtime.threads()
92+
} catch {
93+
lastError = error.localizedDescription
94+
}
95+
}
96+
97+
func runGuidedMemoryDemo() async {
98+
guard !isRunningMemoryDemo else {
99+
return
100+
}
101+
102+
isRunningMemoryDemo = true
103+
lastError = nil
104+
defer {
105+
isRunningMemoryDemo = false
106+
}
107+
108+
do {
109+
let writer = try await runtime.memoryWriter(defaults: DemoMemoryExamples.guidedDefaults)
110+
let record = try await writer.upsert(DemoMemoryExamples.guidedDraft)
111+
let diagnostics = try await writer.diagnostics()
112+
113+
guidedMemoryResult = GuidedMemoryDemoResult(
114+
record: record,
115+
diagnostics: diagnostics
116+
)
117+
} catch {
118+
lastError = error.localizedDescription
119+
}
120+
}
121+
122+
func runRawMemoryDemo() async {
123+
guard !isRunningMemoryDemo else {
124+
return
125+
}
126+
127+
isRunningMemoryDemo = true
128+
lastError = nil
129+
defer {
130+
isRunningMemoryDemo = false
131+
}
132+
133+
do {
134+
let store = try SQLiteMemoryStore(url: AgentDemoRuntimeFactory.defaultMemoryURL())
135+
try await store.upsert(
136+
DemoMemoryExamples.rawRecord,
137+
dedupeKey: DemoMemoryExamples.rawRecord.dedupeKey ?? DemoMemoryExamples.rawRecord.id
138+
)
139+
let diagnostics = try await store.diagnostics(namespace: DemoMemoryExamples.namespace)
140+
141+
rawMemoryResult = RawMemoryDemoResult(
142+
record: DemoMemoryExamples.rawRecord,
143+
diagnostics: diagnostics
144+
)
145+
} catch {
146+
lastError = error.localizedDescription
147+
}
148+
}
149+
150+
func runMemoryPreviewDemo() async {
151+
guard !isRunningMemoryDemo else {
152+
return
153+
}
154+
155+
isRunningMemoryDemo = true
156+
lastError = nil
157+
defer {
158+
isRunningMemoryDemo = false
159+
}
160+
161+
do {
162+
let result: MemoryQueryResult
163+
var previewThreadID: String?
164+
var previewThreadTitle: String?
165+
166+
if session != nil {
167+
let thread = try await runtime.createThread(
168+
title: "Memory Demo: Prompt Injection",
169+
memoryContext: DemoMemoryExamples.previewContext
170+
)
171+
previewThreadID = thread.id
172+
previewThreadTitle = thread.title
173+
result = try await runtime.memoryQueryPreview(
174+
for: thread.id,
175+
request: UserMessageRequest(text: DemoMemoryExamples.previewRequestText)
176+
) ?? MemoryQueryResult(matches: [], truncated: false)
177+
threads = await runtime.threads()
178+
} else {
179+
let store = try SQLiteMemoryStore(url: AgentDemoRuntimeFactory.defaultMemoryURL())
180+
result = try await store.query(
181+
MemoryQuery(
182+
namespace: DemoMemoryExamples.namespace,
183+
scopes: DemoMemoryExamples.previewContext.scopes,
184+
text: DemoMemoryExamples.previewRequestText,
185+
limit: DemoMemoryExamples.previewBudget.maxItems,
186+
maxCharacters: DemoMemoryExamples.previewBudget.maxCharacters
187+
)
188+
)
189+
}
190+
191+
memoryPreviewResult = MemoryPreviewDemoResult(
192+
threadID: previewThreadID,
193+
threadTitle: previewThreadTitle,
194+
requestText: DemoMemoryExamples.previewRequestText,
195+
result: result,
196+
renderedPrompt: DefaultMemoryPromptRenderer().render(
197+
result: result,
198+
budget: DemoMemoryExamples.previewBudget
199+
)
200+
)
201+
} catch {
202+
lastError = error.localizedDescription
203+
}
204+
}
205+
}

0 commit comments

Comments
 (0)