Skip to content

Commit 1fac8b9

Browse files
authored
Add changefeed triggers for summarization and memory processing (#4)
1 parent a23af3a commit 1fac8b9

23 files changed

Lines changed: 1960 additions & 5624 deletions

.env.template

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
# This is a template for the .env file. Copy this file to .env and fill in the values for your accounts.
2+
3+
# ---- Cosmos DB ----
24
COSMOS_DB_ENDPOINT=https://<your-account>.documents.azure.com:443/
5+
# COSMOS_DB__accountEndpoint is required for the Azure Functions change feed trigger
6+
# (identity-based connection). Set it to the same value as COSMOS_DB_ENDPOINT.
7+
COSMOS_DB__accountEndpoint=https://<your-account>.documents.azure.com:443/
38
COSMOS_DB_DATABASE=ai_memory
49
COSMOS_DB_CONTAINER=memories
10+
COSMOS_DB_COUNTERS_CONTAINER=counter
11+
COSMOS_DB_LEASE_CONTAINER=leases
512
COSMOS_DB_AUTOSCALE_MAX_RU=1000
613

14+
# ---- Change Feed Thresholds (set to 0 to disable) ----
15+
THREAD_SUMMARY_EVERY_N=0
16+
FACT_EXTRACTION_EVERY_N=0
17+
USER_SUMMARY_EVERY_N=0
18+
19+
# ---- AI Foundry / Azure OpenAI ----
720
AI_FOUNDRY_ENDPOINT=https://<your-account>.openai.azure.com/
821
AI_FOUNDRY_API_KEY=
922
EMBEDDING_MODEL=text-embedding-3-large
@@ -14,5 +27,6 @@ FULL_TEXT_LANGUAGE=en-US
1427

1528
LLM_MODEL=<your-model-deployment>
1629

30+
# ---- Azure Durable Functions ----
1731
ADF_ENDPOINT=http://localhost:7071/api
1832
ADF_KEY=

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
with:
2222
python-version: ${{ matrix.python-version }}
2323
- name: Install dependencies
24-
run: pip install ruff
24+
run: pip install 'ruff>=0.15,<0.16'
2525
- name: Ruff check
2626
run: ruff check agent_memory_toolkit/ tests/
2727
- name: Ruff format check

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
# Environment
22
.env
33
local.settings.json
4+
.github/prompts/
5+
.github/skills/
46

57
# agents
68
.agents/
79
.agent/
810
.claude/
911
skills/
1012
skills-lock.json
13+
openspec/
14+
openspec*
15+
1116

1217

1318
# Python
@@ -19,6 +24,7 @@ build/
1924
*.egg
2025
.venv/
2126
venv/
27+
.pytest_cache/
2228

2329
# Jupyter Notebook
2430
.ipynb_checkpoints/

Docs/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ This folder contains the main project documentation for Agent Memory Toolkit.
66

77
| Document | Purpose |
88
|----------|---------|
9-
| [concepts.md](concepts.md) | Explains the core memory model, including memory types (turn, summary, fact, user summary), threads, roles, and the processing pipeline. |
10-
| [local_testing.md](local_testing.md) | Covers local setup, environment configuration, RBAC, Cosmos provisioning, and running the toolkit and Azure Functions locally. |
11-
| [azure_testing.md](azure_testing.md) | Covers Azure deployment, cloud configuration, required services, and validation steps for running the toolkit in Azure. |
12-
| [design_patterns.md](design_patterns.md) | Shows when and how to call CRUD operations, summarization, fact extraction, and memory retrieval in chat and multi-agent applications. |
9+
| [concepts.md](concepts.md) | Explains the core memory model, including memory types (turn, summary, fact, user summary), threads, roles, the processing pipeline, and automatic change feed processing. |
10+
| [local_testing.md](local_testing.md) | Covers local setup, environment configuration, RBAC, Cosmos provisioning, running the toolkit and Azure Functions locally, and testing change feed auto-processing. |
11+
| [azure_testing.md](azure_testing.md) | Covers Azure deployment, cloud configuration, required services, change feed settings, and validation steps for running the toolkit in Azure. |
12+
| [design_patterns.md](design_patterns.md) | Shows when and how to call CRUD operations, summarization, fact extraction, and memory retrieval in chat and multi-agent applications, including automatic processing via the change feed. |
1313

1414
## Recommended Reading Order
1515

Docs/azure_testing.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ az functionapp config appsettings set \
111111
LLM_MODEL="gpt-5-mini"
112112
```
113113

114+
### Change feed settings (optional)
115+
116+
To enable automatic processing via the change feed trigger, add these settings:
117+
118+
```bash
119+
az functionapp config appsettings set \
120+
--name <function-app-name> \
121+
--resource-group <resource-group> \
122+
--settings \
123+
COSMOS_DB__accountEndpoint="https://<cosmos-account-name>.documents.azure.com:443/" \
124+
COSMOS_DB_COUNTERS_CONTAINER="counter" \
125+
THREAD_SUMMARY_EVERY_N="5" \
126+
FACT_EXTRACTION_EVERY_N="3" \
127+
USER_SUMMARY_EVERY_N="10"
128+
```
129+
130+
Set any threshold to `"0"` to disable that processing type.
131+
132+
The `leases` container is created automatically by the Azure Functions runtime.
133+
114134
If you use function-key auth for the HTTP trigger, keep the key for the client as `ADF_KEY`.
115135

116136
---
@@ -234,6 +254,7 @@ Bring the environment up in this order:
234254
9. test `generate_thread_summary()`
235255
10. test `extract_facts()` — verify single-line fact output
236256
11. test `generate_user_summary()` / `get_user_summary()`
257+
12. (if change feed is enabled) test automatic processing — write turns and verify derived memories appear
237258

238259
This keeps failures isolated and easier to diagnose.
239260

@@ -264,6 +285,32 @@ print(memory.generate_user_summary(user_id="user-1"))
264285

265286
Thread summaries and user summaries update incrementally: repeated calls merge only new memories into the existing derived document.
266287

288+
### Change feed auto-processing
289+
290+
If you configured the change feed settings, verify automatic processing:
291+
292+
```python
293+
import uuid
294+
295+
# Use a threshold of 3 (THREAD_SUMMARY_EVERY_N=3) for testing
296+
thread_id = str(uuid.uuid4())
297+
for i in range(3):
298+
memory.add_cosmos(
299+
user_id="user-1",
300+
thread_id=thread_id,
301+
role="user",
302+
content=f"Turn {i+1} for change feed validation",
303+
)
304+
305+
# Wait a few seconds for the change feed to trigger, then check:
306+
import time
307+
time.sleep(10)
308+
results = memory.get_memories(user_id="user-1", thread_id=thread_id, memory_type="summary")
309+
print(results) # Should contain an auto-generated summary
310+
```
311+
312+
Check the Function App logs to confirm the `on_memory_change` trigger fired and the orchestrator completed.
313+
267314
### Verify stored results
268315

269316
```python
@@ -293,6 +340,8 @@ Common issues:
293340
| Durable Function starts but fails | Missing app settings or downstream RBAC |
294341
| `No memories found` | No turn memories exist, or all candidate turns predate the existing summary |
295342
| Search is slow | Embedding latency, index choice, or region mismatch |
343+
| Change feed trigger not firing | Verify `COSMOS_DB__accountEndpoint` is set and the function can write to the configured `COSMOS_DB_COUNTERS_CONTAINER` container |
344+
| Auto-processing not starting | Check threshold settings are > 0 in Function App configuration |
296345

297346
Recommended checks:
298347

Docs/concepts.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,59 @@ Prompts for summarization and fact extraction live in `azure_functions/prompts/`
114114

115115
---
116116

117+
## Automatic Processing (Change Feed)
118+
119+
In addition to on-demand processing via the SDK, the toolkit includes a Cosmos DB change feed trigger that **automatically** starts processing orchestrations when enough new turns have been written.
120+
121+
```
122+
memories container
123+
│ (change feed)
124+
125+
on_memory_change trigger
126+
127+
├── count turns per (user_id, thread_id)
128+
│ └── crosses threshold? ──► start thread_summary / extract_facts
129+
130+
└── count turns per user_id
131+
└── crosses threshold? ──► start user_summary
132+
```
133+
134+
### How it works
135+
136+
1. The change feed trigger watches the `memories` container for new documents.
137+
2. Only documents with `type == "turn"` are counted (summaries, facts, and user summaries are ignored).
138+
3. Documents in the dedicated `counter` container track how many turns have been seen per scope using ETag-based optimistic concurrency.
139+
4. When a counter crosses a configured threshold, the corresponding Durable Functions orchestration is started automatically.
140+
141+
### Threshold settings
142+
143+
| Setting | Scope | Default |
144+
|---------|-------|---------|
145+
| `THREAD_SUMMARY_EVERY_N` | Per `(user_id, thread_id)` | `0` (disabled) |
146+
| `FACT_EXTRACTION_EVERY_N` | Per `(user_id, thread_id)` | `0` (disabled) |
147+
| `USER_SUMMARY_EVERY_N` | Per `user_id` (across all threads) | `0` (disabled) |
148+
149+
Set any value to `0` to disable that processing type. For example, setting `THREAD_SUMMARY_EVERY_N=5` generates a thread summary every 5 new turns in each thread.
150+
151+
### Required containers
152+
153+
| Container | Partition Key | Purpose |
154+
|-----------|---------------|---------|
155+
| `memories` | `/user_id`, `/thread_id` (hierarchical) | Existing memory store |
156+
| `counter` | `/user_id`, `/thread_id` (hierarchical) | Message count tracking for automatic processing |
157+
| `leases` | `/id` | Auto-created by the trigger for change feed checkpointing |
158+
159+
### Push vs. pull
160+
161+
| Mode | Trigger | Use case |
162+
|------|---------|----------|
163+
| **On-demand (pull)** | SDK call (`generate_thread_summary()`, etc.) | Explicit control over when processing happens |
164+
| **Automatic (push)** | Change feed trigger | Fire-and-forget — processing happens in the background as turns are written |
165+
166+
Both modes use the same Durable Functions orchestrator and activities, so prompts, incremental update logic, and stored outputs are identical.
167+
168+
---
169+
117170
## Local vs. Cloud Storage
118171

119172
| Backend | Use Case | Persistence |

Docs/design_patterns.md

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ await mem.delete_cosmos(memory_id="<id>", user_id="user-1", thread_id=THREAD_ID)
8686
- **End of conversation** — after the user closes a session or a support ticket is resolved.
8787
- **Long-running thread** — when a thread exceeds a token budget (e.g. > 50 turns) and you need a compact representation for context.
8888
- **Periodic background job** — on a schedule to keep summaries up to date for active threads.
89+
- **Automatic (change feed)** — set `THREAD_SUMMARY_EVERY_N` and the change feed trigger handles it. See [Section 8](#8-automatic-processing-with-change-feed).
8990

9091
Summaries are incremental: if one already exists for the thread, only newer turns are merged in.
9192

@@ -111,6 +112,7 @@ The summary is stored automatically in Cosmos with id `summary_user-1_thread-abc
111112
- **After each meaningful exchange** — extract facts from the latest turns so they are available for retrieval immediately.
112113
- **End of conversation** — capture all discrete preferences, decisions, and requirements from the thread.
113114
- **Before a planning step** — in multi-agent workflows, extract facts before handing context to a planner agent.
115+
- **Automatic (change feed)** — set `FACT_EXTRACTION_EVERY_N` and the change feed trigger handles it. See [Section 8](#8-automatic-processing-with-change-feed).
114116

115117
Each fact is stored as its own document with its own embedding, making it ideal for fine-grained semantic search.
116118

@@ -133,6 +135,7 @@ result = await mem.extract_facts(
133135
- **Cross-session onboarding** — at the start of a new thread, generate (or update) the user summary so the agent has context from all prior conversations.
134136
- **After a thread summary is created** — chain it: summarize the thread, then update the user summary.
135137
- **On a schedule** — for users with many threads, run periodically to keep the profile current.
138+
- **Automatic (change feed)** — set `USER_SUMMARY_EVERY_N` and the change feed trigger handles it. See [Section 8](#8-automatic-processing-with-change-feed).
136139

137140
User summaries are also incremental. The pipeline merges only new thread data into the existing profile.
138141

@@ -311,6 +314,50 @@ await mem.generate_user_summary(
311314

312315
---
313316

317+
## 8. Automatic Processing with Change Feed
318+
319+
Instead of calling `generate_thread_summary()`, `extract_facts()`, or `generate_user_summary()` explicitly, you can let the Cosmos DB change feed trigger fire them automatically in the background.
320+
321+
### How it works
322+
323+
When a new turn is written to the `memories` container, the change feed trigger:
324+
325+
1. Increments a counter document in the dedicated `counter` container for each relevant scope.
326+
2. Checks whether the counter has crossed a configured threshold.
327+
3. Starts the appropriate Durable Functions orchestration if the threshold is crossed.
328+
329+
### Configuration
330+
331+
Set these application settings (in `local.settings.json` locally or Function App settings in Azure):
332+
333+
| Setting | Scope | Effect |
334+
|---------|-------|--------|
335+
| `THREAD_SUMMARY_EVERY_N=5` | Per `(user_id, thread_id)` | Summarize the thread every 5 turns |
336+
| `FACT_EXTRACTION_EVERY_N=3` | Per `(user_id, thread_id)` | Extract facts every 3 turns |
337+
| `USER_SUMMARY_EVERY_N=10` | Per `user_id` | Update user profile every 10 turns across all threads |
338+
339+
Set any value to `0` to disable that processing type. All three default to `0` (disabled).
340+
341+
### Required infrastructure
342+
343+
The change feed trigger needs two additional Cosmos DB containers beyond the existing `memories` container:
344+
345+
- **`counter`** — stores lightweight per-thread and per-user message counters used for threshold checks
346+
- **`leases`** — auto-created by the Azure Functions runtime for change feed checkpointing
347+
348+
The `COSMOS_DB__accountEndpoint` setting must also be configured for the identity-based change feed binding.
349+
350+
### When to use automatic vs. on-demand
351+
352+
| Approach | Best for |
353+
|----------|----------|
354+
| **On-demand** | Full control, testing, one-off processing, chaining operations |
355+
| **Automatic** | Always-on background processing, fire-and-forget, production workloads |
356+
357+
Both approaches use the same orchestrator and activities, so the output is identical.
358+
359+
---
360+
314361
## Quick Reference
315362

316363
| Operation | Method | When |
@@ -321,7 +368,7 @@ await mem.generate_user_summary(
321368
| Delete a memory | `delete_cosmos` | Remove incorrect or sensitive data |
322369
| Get a thread | `get_thread` | Load recent conversation context |
323370
| Semantic search | `search_cosmos` | Find relevant facts or summaries for a prompt |
324-
| Summarize a thread | `generate_thread_summary` | End of conversation or periodically |
325-
| Extract facts | `extract_facts` | After key exchanges or end of conversation |
326-
| Summarize a user | `generate_user_summary` | Cross-session profiling, after thread summaries |
371+
| Summarize a thread | `generate_thread_summary` | End of conversation, periodically, or automatic via change feed |
372+
| Extract facts | `extract_facts` | After key exchanges, end of conversation, or automatic via change feed |
373+
| Summarize a user | `generate_user_summary` | Cross-session profiling, after thread summaries, or automatic via change feed |
327374
| Get user summary | `get_user_summary` | Start of a new session |

0 commit comments

Comments
 (0)