Skip to content

Commit 2a011ba

Browse files
committed
memory stateful mcp
1 parent 66fcab5 commit 2a011ba

5 files changed

Lines changed: 152 additions & 74 deletions

File tree

openai_agents/mcp/README.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,6 @@ uv run openai_agents/mcp/run_file_system_stateless_workflow.py
2323

2424
This sample assumes that the worker and `run_file_system_workflow.py` are on the same machine.
2525

26-
### Sequential Thinking MCP - Stateful
27-
28-
This example demonstrates a stateful MCP server that maintains a sequential plan across multiple agent runs within a single workflow execution.
29-
30-
Note: This example is not a direct port from the OpenAI SDK samples; it is added here because it better illustrates stateful behavior than the filesystem server.
31-
32-
First, start the worker:
33-
```bash
34-
uv run openai_agents/mcp/run_sequentialthinking_stateful_worker.py
35-
```
36-
37-
Run the workflow:
38-
```bash
39-
uv run openai_agents/mcp/run_sequentialthinking_stateful_workflow.py
40-
```
4126

4227
### Streamable HTTP MCP - Stateless
4328

@@ -89,3 +74,18 @@ Finally, run the workflow:
8974
```bash
9075
uv run openai_agents/mcp/run_prompt_server_stateless_workflow.py
9176
```
77+
78+
79+
### Memory MCP - Stateful (Research Scratchpad)
80+
81+
Demonstrates durable note-taking with the Memory MCP server: write seed notes, query by tags, synthesize a brief with citations, then update and delete notes.
82+
83+
Start the worker:
84+
```bash
85+
uv run openai_agents/mcp/run_memory_stateful_worker.py
86+
```
87+
88+
Run the research scratchpad workflow:
89+
```bash
90+
uv run openai_agents/mcp/run_memory_research_scratchpad_workflow.py
91+
```

openai_agents/mcp/run_sequentialthinking_stateful_workflow.py renamed to openai_agents/mcp/run_memory_research_scratchpad_workflow.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
from temporalio.client import Client
66
from temporalio.contrib.openai_agents import OpenAIAgentsPlugin
77

8-
from openai_agents.mcp.workflows.sequentialthinking_stateful_workflow import (
9-
SequentialThinkingWorkflow,
8+
from openai_agents.mcp.workflows.memory_research_scratchpad_workflow import (
9+
MemoryResearchScratchpadWorkflow,
1010
)
1111

1212

@@ -17,12 +17,12 @@ async def main():
1717
)
1818

1919
result = await client.execute_workflow(
20-
SequentialThinkingWorkflow.run,
21-
id="sequentialthinking-stateful-workflow",
22-
task_queue="openai-agents-mcp-sequential-stateful-task-queue",
20+
MemoryResearchScratchpadWorkflow.run,
21+
id="memory-research-scratchpad-workflow",
22+
task_queue="openai-agents-mcp-memory-stateful-task-queue",
2323
)
2424

25-
print(f"Result: {result}")
25+
print(f"Result:\n{result}")
2626

2727

2828
if __name__ == "__main__":

openai_agents/mcp/run_sequentialthinking_stateful_worker.py renamed to openai_agents/mcp/run_memory_stateful_worker.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,41 +13,42 @@
1313
)
1414
from temporalio.worker import Worker
1515

16-
from openai_agents.mcp.workflows.sequentialthinking_stateful_workflow import (
17-
SequentialThinkingWorkflow,
16+
from openai_agents.mcp.workflows.memory_research_scratchpad_workflow import (
17+
MemoryResearchScratchpadWorkflow,
1818
)
1919

2020

2121
async def main():
2222
logging.basicConfig(level=logging.INFO)
2323

24-
sequential_server_provider = StatefulMCPServerProvider(
24+
memory_server_provider = StatefulMCPServerProvider(
2525
lambda: MCPServerStdio(
26-
name="SequentialThinkingServer",
26+
name="MemoryServer",
2727
params={
2828
"command": "npx",
29-
"args": ["-y", "@modelcontextprotocol/server-sequential-thinking"],
29+
"args": ["-y", "@modelcontextprotocol/server-memory"],
3030
},
3131
)
3232
)
3333

34+
# Create client connected to server at the given address
3435
client = await Client.connect(
3536
"localhost:7233",
3637
plugins=[
3738
OpenAIAgentsPlugin(
3839
model_params=ModelActivityParameters(
3940
start_to_close_timeout=timedelta(seconds=60)
4041
),
41-
mcp_servers=[sequential_server_provider],
42+
mcp_servers=[memory_server_provider],
4243
),
4344
],
4445
)
4546

4647
worker = Worker(
4748
client,
48-
task_queue="openai-agents-mcp-sequential-stateful-task-queue",
49+
task_queue="openai-agents-mcp-memory-stateful-task-queue",
4950
workflows=[
50-
SequentialThinkingWorkflow,
51+
MemoryResearchScratchpadWorkflow,
5152
],
5253
activities=[],
5354
)
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from __future__ import annotations
2+
3+
from agents import Agent, Runner, trace
4+
from agents.model_settings import ModelSettings
5+
from temporalio import workflow
6+
from temporalio.contrib import openai_agents as temporal_openai_agents
7+
8+
SEED_NOTES = [
9+
(
10+
"scratchpad/ai-sum/001",
11+
"Study A (2024-04)",
12+
"Summaries reduced triage time by 22% (n=60).",
13+
["ai-summarization", "email", "kpi"],
14+
),
15+
(
16+
"scratchpad/ai-sum/002",
17+
"User preference",
18+
"Users prefer action-first summaries with 5–8 bullets max.",
19+
["ai-summarization", "email", "ux"],
20+
),
21+
(
22+
"scratchpad/ai-sum/003",
23+
"Risk: misleading summaries",
24+
"Hallucination risk; mitigation: confidence thresholds + easy fallback to original email.",
25+
["ai-summarization", "email", "risk"],
26+
),
27+
(
28+
"scratchpad/ai-sum/004",
29+
"Latency consideration",
30+
"Cold-start latency noticeable on first open; can cache or precompute in background.",
31+
["ai-summarization", "email", "perf"],
32+
),
33+
(
34+
"scratchpad/ai-sum/005",
35+
"Adoption insight",
36+
"Admin controls improve enterprise adoption; opt-in increases trust and perceived control.",
37+
["ai-summarization", "email", "adoption"],
38+
),
39+
]
40+
41+
42+
@workflow.defn
43+
class MemoryResearchScratchpadWorkflow:
44+
@workflow.run
45+
async def run(self) -> str:
46+
async with temporal_openai_agents.workflow.stateful_mcp_server(
47+
"MemoryServer",
48+
) as server:
49+
with trace(workflow_name="MCP Memory Scratchpad Example"):
50+
agent = Agent(
51+
name="Research Scratchpad Agent",
52+
instructions=(
53+
"Use the Memory MCP tools to persist, query, update, and delete notes."
54+
" Keep IDs short and consistent. Synthesis must rely only on recalled notes and include simple"
55+
" citations of the form '(Note: id)'. Keep the brief to 5 bullets."
56+
),
57+
mcp_servers=[server],
58+
model_settings=ModelSettings(tool_choice="required"),
59+
)
60+
61+
# Step 1: Write seed notes to memory
62+
write_prompt_lines = [
63+
"Store the following notes in memory. Use the given id and tags for each entry.",
64+
"After storing, confirm each (id, tags) that was written.",
65+
"",
66+
]
67+
for note_id, title, content, tags in SEED_NOTES:
68+
# Store tags as separate observation lines so search can reliably match them
69+
tag_obs = ", ".join([f"tag: {t}" for t in tags])
70+
write_prompt_lines.append(
71+
f"- id: {note_id}; title: {title}; content: {content}; observations: [{tag_obs}]"
72+
)
73+
write_prompt = "\n".join(write_prompt_lines)
74+
workflow.logger.info("Writing seed notes to memory")
75+
r1 = await Runner.run(starting_agent=agent, input=write_prompt)
76+
77+
# Step 2: Query by tags
78+
query_prompt = (
79+
"Search memory for notes that contain BOTH observations 'tag: ai-summarization' and 'tag: email'. "
80+
"If the search returns empty, list entities with the name prefix 'scratchpad/ai-sum/' and filter to those that have both tag observations. "
81+
"For the resulting ids, call retrieve_entities to fetch their observations, then return a normalized list of (id, title, 1–2 key points) based on the retrieved entities."
82+
)
83+
workflow.logger.info("Querying notes by tags")
84+
r2 = await Runner.run(
85+
starting_agent=agent,
86+
input=query_prompt,
87+
previous_response_id=r1.last_response_id,
88+
)
89+
90+
# Step 3: Synthesis with citations
91+
synth_prompt = (
92+
"Using only the recalled notes, produce a 5-bullet brief. "
93+
"Include one citation per bullet in the form '(Note: id)'. Do not introduce new facts."
94+
)
95+
workflow.logger.info("Synthesizing brief from recalled notes")
96+
r3 = await Runner.run(
97+
starting_agent=agent,
98+
input=synth_prompt,
99+
previous_response_id=r2.last_response_id,
100+
)
101+
102+
# Step 4: Update and re-query (optional demonstration)
103+
update_prompt = (
104+
"Update the note 'scratchpad/ai-sum/003' to include more precise mitigation:"
105+
" 'threshold=0.7; fallback to full email on low confidence'. Then delete the note"
106+
" 'scratchpad/ai-sum/005'. Finally, list only the remaining 'risk' notes with (id, updated content)."
107+
)
108+
workflow.logger.info(
109+
"Updating one note and deleting another, then re-listing risk notes"
110+
)
111+
r4 = await Runner.run(
112+
starting_agent=agent,
113+
input=update_prompt,
114+
previous_response_id=r3.last_response_id,
115+
)
116+
117+
return (
118+
f"WRITE CONFIRMATIONS:\n{r1.final_output}\n\n"
119+
f"QUERY RESULTS:\n{r2.final_output}\n\n"
120+
f"SYNTHESIS:\n{r3.final_output}\n\n"
121+
f"UPDATES:\n{r4.final_output}"
122+
)

openai_agents/mcp/workflows/sequentialthinking_stateful_workflow.py

Lines changed: 0 additions & 45 deletions
This file was deleted.

0 commit comments

Comments
 (0)