Memory System
The Memory System provides both short-term and long-term memory capabilities for Device Agents in UFO3. The system consists of two primary components: Memory (agent-specific execution history) and Blackboard (shared multi-agent communication). This dual-memory architecture enables agents to maintain their own execution context while coordinating seamlessly across devices and sessions.
Overview
The Memory System supports the Device Agent architecture through two distinct but complementary mechanisms:
graph TB
subgraph "Memory System Architecture"
Agent1[Agent Instance]
Agent2[Agent Instance]
AgentN[Agent Instance N]
Memory1[Memory<br/>Short-term]
Memory2[Memory<br/>Short-term]
MemoryN[Memory<br/>Short-term]
Blackboard[Blackboard<br/>Long-term Shared]
Agent1 --> Memory1
Agent2 --> Memory2
AgentN --> MemoryN
Agent1 -.Share.-> Blackboard
Agent2 -.Share.-> Blackboard
AgentN -.Share.-> Blackboard
Blackboard -.Read.-> Agent1
Blackboard -.Read.-> Agent2
Blackboard -.Read.-> AgentN
end
style Memory1 fill:#e1f5ff
style Memory2 fill:#e1f5ff
style MemoryN fill:#e1f5ff
style Blackboard fill:#fff4e1
| Component |
Scope |
Persistence |
Primary Use Case |
| Memory |
Agent-specific |
Session lifetime |
Execution history, context tracking |
| Blackboard |
Multi-agent shared |
Configurable (file-backed) |
Cross-agent coordination, information sharing |
Design Benefits:
- Separation of Concerns: Agent-specific history isolated from shared state
- Scalability: Each agent manages own memory independently
- Coordination: Blackboard enables multi-agent communication without tight coupling
- Persistence: Blackboard can survive session restarts (file-backed storage)
Memory (Short-term Agent Memory)
The Memory class manages the short-term execution history of a single agent. Each agent instance has its own Memory that records every interaction step, forming a chronological execution trace.
Memory Architecture
graph LR
subgraph "Memory Lifecycle"
Step1[Step 1<br/>MemoryItem]
Step2[Step 2<br/>MemoryItem]
Step3[Step 3<br/>MemoryItem]
StepN[Step N<br/>MemoryItem]
Step1 --> Step2
Step2 --> Step3
Step3 --> StepN
end
subgraph "MemoryItem Contents"
Screenshot[Screenshot]
Action[Action Taken]
Result[Execution Result]
Observation[UI Observation]
Cost[LLM Cost]
end
StepN --> Screenshot
StepN --> Action
StepN --> Result
StepN --> Observation
StepN --> Cost
style Step1 fill:#e1f5ff
style Step2 fill:#e1f5ff
style Step3 fill:#e1f5ff
style StepN fill:#e1f5ff
MemoryItem Structure
A MemoryItem is a flexible dataclass that represents a single execution step in the agent's history. The structure is customizable to accommodate different agent types and platforms.
This data class represents a memory item of an agent at one step.
attributes
property
Get the attributes of the memory item.
add_values_from_dict(values)
Add fields to the memory item.
| Parameters: |
-
values
(Dict[str, Any])
–
The values of the fields.
|
Source code in agents/memory/memory.py
| def add_values_from_dict(self, values: Dict[str, Any]) -> None:
"""
Add fields to the memory item.
:param values: The values of the fields.
"""
for key, value in values.items():
self.set_value(key, value)
|
filter(keys=[])
Fetch the memory item.
| Parameters: |
-
keys
(List[str], default:
[]
)
–
|
| Returns: |
-
None
–
The filtered memory item.
|
Source code in agents/memory/memory.py
| def filter(self, keys: List[str] = []) -> None:
"""
Fetch the memory item.
:param keys: The keys to fetch.
:return: The filtered memory item.
"""
return {key: value for key, value in self.to_dict().items() if key in keys}
|
from_dict(data)
Convert the dictionary to a MemoryItem.
Source code in agents/memory/memory.py
| def from_dict(self, data: Dict[str, str]) -> None:
"""
Convert the dictionary to a MemoryItem.
:param data: The dictionary.
"""
for key, value in data.items():
self.set_value(key, value)
|
get_value(key)
Get the value of the field.
Source code in agents/memory/memory.py
| def get_value(self, key: str) -> Optional[str]:
"""
Get the value of the field.
:param key: The key of the field.
:return: The value of the field.
"""
return getattr(self, key, None)
|
get_values(keys)
Get the values of the fields.
| Returns: |
-
dict
–
The values of the fields.
|
Source code in agents/memory/memory.py
| def get_values(self, keys: List[str]) -> dict:
"""
Get the values of the fields.
:param keys: The keys of the fields.
:return: The values of the fields.
"""
return {key: self.get_value(key) for key in keys}
|
set_value(key, value)
Add a field to the memory item.
| Parameters: |
-
key
(str)
–
-
value
(str)
–
|
Source code in agents/memory/memory.py
55
56
57
58
59
60
61
62
63
64 | def set_value(self, key: str, value: str) -> None:
"""
Add a field to the memory item.
:param key: The key of the field.
:param value: The value of the field.
"""
setattr(self, key, value)
if key not in self._memory_attributes:
self._memory_attributes.append(key)
|
to_dict()
Convert the MemoryItem to a dictionary.
Source code in agents/memory/memory.py
19
20
21
22
23
24
25
26
27
28
29 | def to_dict(self) -> Dict[str, str]:
"""
Convert the MemoryItem to a dictionary.
:return: The dictionary.
"""
return {
key: value
for key, value in self.__dict__.items()
if key in self._memory_attributes
}
|
to_json()
Convert the memory item to a JSON string.
Source code in agents/memory/memory.py
| def to_json(self) -> str:
"""
Convert the memory item to a JSON string.
:return: The JSON string.
"""
return json.dumps(self.to_dict())
|
Common MemoryItem Fields
| Field |
Type |
Description |
Usage in Strategies |
step |
int |
Execution step number |
Tracking execution progress |
screenshot |
str (path) |
Screenshot file path |
Visual context for LLM reasoning |
action |
str |
Action function name |
Execution history, replay |
arguments |
Dict[str, Any] |
Action arguments |
Debugging, audit logging |
results |
List[Result] |
Command execution results |
Success/failure tracking |
observation |
str |
UI element descriptions |
LLM prompt context |
control_text |
str |
UI text content |
Element identification |
request |
str |
User request at this step |
Task context |
response |
str |
LLM raw response |
Debugging LLM decisions |
parsed_response |
Dict |
Parsed LLM output |
Structured action extraction |
cost |
float |
LLM API cost |
Budget tracking |
error |
Optional[str] |
Error message if failed |
Error recovery |
Example: Creating a MemoryItem
from ufo.agents.memory.memory import MemoryItem
# After executing a step, create memory item
memory_item = MemoryItem(
step=3,
screenshot="screenshots/step_3.png",
action="click_element",
arguments={"element_id": "submit_button"},
results=[Result(status=ResultStatus.SUCCESS, result="Button clicked")],
observation="Submit button located at (500, 300)",
request="Submit the form",
response='{"action": "click_element", "element": "submit_button"}',
parsed_response={"action": "click_element", "element": "submit_button"},
cost=0.0023
)
Note on Flexible Schema:
MemoryItem uses a flexible dataclass structure. Agent implementations can add custom fields based on their specific requirements. For example, Windows agents might add ui_automation_info, while Linux agents might add shell_output.
Memory Class
The Memory class manages a list of MemoryItem instances, providing methods to add, retrieve, and filter execution history.
This data class represents a memory of an agent.
content
property
Get the content of the memory.
length
property
Get the length of the memory.
| Returns: |
-
int
–
The length of the memory.
|
list_content
property
List the content of the memory.
| Returns: |
-
List[Dict[str, str]]
–
The content of the memory.
|
add_memory_item(memory_item)
Add a memory item to the memory.
Source code in agents/memory/memory.py
| def add_memory_item(self, memory_item: MemoryItem) -> None:
"""
Add a memory item to the memory.
:param memory_item: The memory item to add.
"""
self._content.append(memory_item)
|
clear()
Clear the memory.
Source code in agents/memory/memory.py
| def clear(self) -> None:
"""
Clear the memory.
"""
self._content = []
|
delete_memory_item(step)
Delete a memory item from the memory.
| Parameters: |
-
step
(int)
–
The step of the memory item to delete.
|
Source code in agents/memory/memory.py
| def delete_memory_item(self, step: int) -> None:
"""
Delete a memory item from the memory.
:param step: The step of the memory item to delete.
"""
self._content = [item for item in self._content if item.step != step]
|
filter_memory_from_keys(keys)
Filter the memory from the keys. If an item does not have the key, the key will be ignored.
Source code in agents/memory/memory.py
123
124
125
126
127
128
129 | def filter_memory_from_keys(self, keys: List[str]) -> List[Dict[str, str]]:
"""
Filter the memory from the keys. If an item does not have the key, the key will be ignored.
:param keys: The keys to filter.
:return: The filtered memory.
"""
return [item.filter(keys) for item in self._content]
|
filter_memory_from_steps(steps)
Filter the memory from the steps.
Source code in agents/memory/memory.py
115
116
117
118
119
120
121 | def filter_memory_from_steps(self, steps: List[int]) -> List[Dict[str, str]]:
"""
Filter the memory from the steps.
:param steps: The steps to filter.
:return: The filtered memory.
"""
return [item.to_dict() for item in self._content if item.step in steps]
|
from_list_of_dicts(data)
Convert the list of dictionaries to the memory.
| Parameters: |
-
data
(List[Dict[str, str]])
–
The list of dictionaries.
|
Source code in agents/memory/memory.py
176
177
178
179
180
181
182
183
184
185 | def from_list_of_dicts(self, data: List[Dict[str, str]]) -> None:
"""
Convert the list of dictionaries to the memory.
:param data: The list of dictionaries.
"""
self._content = []
for item in data:
memory_item = MemoryItem()
memory_item.from_dict(item)
self._content.append(memory_item)
|
get_latest_item()
Get the latest memory item.
Source code in agents/memory/memory.py
187
188
189
190
191
192
193
194 | def get_latest_item(self) -> MemoryItem:
"""
Get the latest memory item.
:return: The latest memory item.
"""
if self.length == 0:
return None
return self._content[-1]
|
is_empty()
Check if the memory is empty.
| Returns: |
-
bool
–
The boolean value indicating if the memory is empty.
|
Source code in agents/memory/memory.py
| def is_empty(self) -> bool:
"""
Check if the memory is empty.
:return: The boolean value indicating if the memory is empty.
"""
return self.length == 0
|
load(content)
Load the data from the memory.
Source code in agents/memory/memory.py
| def load(self, content: List[MemoryItem]) -> None:
"""
Load the data from the memory.
:param content: The content to load.
"""
self._content = content
|
to_json()
Convert the memory to a JSON string.
Source code in agents/memory/memory.py
159
160
161
162
163
164
165
166
167 | def to_json(self) -> str:
"""
Convert the memory to a JSON string.
:return: The JSON string.
"""
return json.dumps(
[item.to_dict() for item in self._content if item is not None]
)
|
to_list_of_dicts()
Convert the memory to a list of dictionaries.
| Returns: |
-
List[Dict[str, str]]
–
The list of dictionaries.
|
Source code in agents/memory/memory.py
| def to_list_of_dicts(self) -> List[Dict[str, str]]:
"""
Convert the memory to a list of dictionaries.
:return: The list of dictionaries.
"""
return [item.to_dict() for item in self._content]
|
Key Methods
| Method |
Purpose |
Usage |
add_memory_item(item) |
Append new execution step |
Called by MEMORY_UPDATE strategy after each step |
get_latest_item() |
Retrieve the most recent item |
Get the last execution step |
filter_memory_from_keys(keys) |
Filter items by specific keys |
Build LLM prompt with selected fields |
filter_memory_from_steps(steps) |
Filter items by step numbers |
Retrieve specific execution steps |
clear() |
Reset memory |
New session initialization |
is_empty() |
Check if memory is empty |
Validate memory state |
Example: Using Memory in Processor
from ufo.agents.processors.strategies.memory_strategies import MemoryUpdateStrategy
from ufo.agents.memory.memory import Memory, MemoryItem
class AppAgentProcessor(ProcessorTemplate):
def __init__(self, agent, context):
super().__init__(agent, context)
self.memory = Memory() # Agent-specific memory
# MEMORY_UPDATE strategy adds items to memory
self.register_strategy(
ProcessingPhase.MEMORY_UPDATE,
MemoryUpdateStrategy(agent, context, self.memory)
)
def get_prompt_context(self) -> str:
"""Build LLM prompt with recent execution history."""
# Get recent steps using content property
all_steps = self.memory.content
recent_steps = all_steps[-5:] if len(all_steps) > 5 else all_steps
context = "## Recent Execution History:\n"
for item in recent_steps:
context += f"Step {item.get_value('step')}: {item.get_value('action')}"
context += f"({item.get_value('arguments')}) -> {item.get_value('results')}\n"
return context
Memory Lifecycle
sequenceDiagram
participant Processor
participant Memory
participant MemoryUpdateStrategy
Note over Processor: Agent starts session
Processor->>Memory: Initialize Memory()
loop Each Execution Step
Note over Processor: Execute strategies
Processor->>MemoryUpdateStrategy: execute()
MemoryUpdateStrategy->>MemoryUpdateStrategy: Create MemoryItem from context
MemoryUpdateStrategy->>Memory: add_memory_item(item)
Memory->>Memory: Append to internal list
end
Note over Processor: Need prompt context
Processor->>Memory: content property (get all)
Memory-->>Processor: List[MemoryItem]
Note over Processor: Session ends
Processor->>Memory: clear()
Memory Management Best Practices:
- Limited Context: When building LLM prompts, use the content property and slice for recent items to avoid token limits
- Selective Fields: Only include relevant MemoryItem fields in prompts (e.g., action + results, not raw screenshots)
- Error Analysis: Use filter_memory_from_keys() to extract specific information patterns
Blackboard (Long-term Shared Memory)
The Blackboard class implements the Blackboard Pattern for multi-agent coordination. It provides a shared memory space where agents can read and write information that persists across sessions and is accessible to all agents.
Blackboard Pattern
The Blackboard Pattern is a well-known architectural pattern for multi-agent systems:
graph TB
subgraph "Blackboard Pattern"
BB[Blackboard<br/>Shared Knowledge Space]
HostAgent[HostAgent<br/>Windows]
AppAgent[AppAgent<br/>Windows]
HostAgent -->|Write: questions| BB
HostAgent -->|Write: requests| BB
AppAgent -->|Read: requests| BB
AppAgent -->|Write: trajectories| BB
BB -.Persist.-> FileStorage[(JSON/JSONL)]
end
style BB fill:#fff4e1
style HostAgent fill:#e1f5ff
style AppAgent fill:#e1f5ff
Blackboard Pattern Characteristics:
- Centralized Knowledge: All agents read/write from a single shared space
- Loose Coupling: Agents don't directly communicate; they interact via blackboard
- Opportunistic Scheduling: Agents can act when relevant information appears on blackboard
- Persistence: Knowledge survives agent restarts and session boundaries
Blackboard Architecture
The Blackboard is organized with four main memory components, each storing a list of MemoryItem objects:
# Blackboard internal structure
class Blackboard:
_questions: Memory # Q&A pairs with user
_requests: Memory # Historical user requests
_trajectories: Memory # Step-wise execution history
_screenshots: Memory # Important screenshots
Each component is a Memory object that stores MemoryItem instances with flexible key-value pairs.
Blackboard Class
Class for the blackboard, which stores the data and images which are visible to all the agents.
Initialize the blackboard.
Source code in agents/memory/blackboard.py
41
42
43
44
45
46
47
48
49
50
51
52
53 | def __init__(self) -> None:
"""
Initialize the blackboard.
"""
self._questions: Memory = Memory()
self._requests: Memory = Memory()
self._trajectories: Memory = Memory()
self._screenshots: Memory = Memory()
if ufo_config.system.use_customization:
self.load_questions(
ufo_config.system.qa_pair_file, ufo_config.system.qa_pair_num
)
|
questions
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The questions from the blackboard.
|
requests
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The requests from the blackboard.
|
screenshots
property
Get the images from the blackboard.
| Returns: |
-
Memory
–
The images from the blackboard.
|
trajectories
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The trajectories from the blackboard.
|
add_data(data, memory)
Add the data to the a memory in the blackboard.
| Parameters: |
-
data
(Union[MemoryItem, Dict[str, str], str])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
-
memory
(Memory)
–
The memory to add the data to.
|
Source code in agents/memory/blackboard.py
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 | def add_data(
self, data: Union[MemoryItem, Dict[str, str], str], memory: Memory
) -> None:
"""
Add the data to the a memory in the blackboard.
:param data: The data to be added. It can be a dictionary or a MemoryItem or a string.
:param memory: The memory to add the data to.
"""
if isinstance(data, dict):
data_memory = MemoryItem()
data_memory.add_values_from_dict(data)
memory.add_memory_item(data_memory)
elif isinstance(data, MemoryItem):
memory.add_memory_item(data)
elif isinstance(data, str):
data_memory = MemoryItem()
data_memory.add_values_from_dict({"text": data})
memory.add_memory_item(data_memory)
else:
print(f"Warning: Unsupported data type: {type(data)} when adding data.")
|
add_image(screenshot_path='', metadata=None)
Add the image to the blackboard.
| Parameters: |
-
screenshot_path
(str, default:
''
)
–
-
metadata
(Optional[Dict[str, str]], default:
None
)
–
The metadata of the image.
|
Source code in agents/memory/blackboard.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 | def add_image(
self,
screenshot_path: str = "",
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Add the image to the blackboard.
:param screenshot_path: The path of the image.
:param metadata: The metadata of the image.
"""
if os.path.exists(screenshot_path):
screenshot_str = utils.encode_image_from_path(screenshot_path)
else:
print(f"Screenshot path {screenshot_path} does not exist.")
screenshot_str = ""
image_memory_item = ImageMemoryItem()
image_memory_item.add_values_from_dict(
{
ImageMemoryItemNames.METADATA: metadata.get(
ImageMemoryItemNames.METADATA
),
ImageMemoryItemNames.IMAGE_PATH: screenshot_path,
ImageMemoryItemNames.IMAGE_STR: screenshot_str,
}
)
self.screenshots.add_memory_item(image_memory_item)
|
add_questions(questions)
Add the data to the blackboard.
| Parameters: |
-
questions
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
109
110
111
112
113
114
115 | def add_questions(self, questions: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param questions: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(questions, self.questions)
|
add_requests(requests)
Add the data to the blackboard.
| Parameters: |
-
requests
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
117
118
119
120
121
122
123 | def add_requests(self, requests: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param requests: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(requests, self.requests)
|
add_trajectories(trajectories)
Add the data to the blackboard.
| Parameters: |
-
trajectories
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
125
126
127
128
129
130
131 | def add_trajectories(self, trajectories: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param trajectories: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(trajectories, self.trajectories)
|
blackboard_from_dict(blackboard_dict)
Convert the dictionary to the blackboard.
| Parameters: |
-
blackboard_dict
(Dict[str, List[Dict[str, str]]])
–
|
Source code in agents/memory/blackboard.py
262
263
264
265
266
267
268
269
270
271
272 | def blackboard_from_dict(
self, blackboard_dict: Dict[str, List[Dict[str, str]]]
) -> None:
"""
Convert the dictionary to the blackboard.
:param blackboard_dict: The dictionary.
"""
self.questions.from_list_of_dicts(blackboard_dict.get("questions", []))
self.requests.from_list_of_dicts(blackboard_dict.get("requests", []))
self.trajectories.from_list_of_dicts(blackboard_dict.get("trajectories", []))
self.screenshots.from_list_of_dicts(blackboard_dict.get("screenshots", []))
|
blackboard_to_dict()
Convert the blackboard to a dictionary.
| Returns: |
-
Dict[str, List[Dict[str, str]]]
–
The blackboard in the dictionary format.
|
Source code in agents/memory/blackboard.py
241
242
243
244
245
246
247
248
249
250
251
252
253 | def blackboard_to_dict(self) -> Dict[str, List[Dict[str, str]]]:
"""
Convert the blackboard to a dictionary.
:return: The blackboard in the dictionary format.
"""
blackboard_dict = {
"questions": self.questions.to_list_of_dicts(),
"requests": self.requests.to_list_of_dicts(),
"trajectories": self.trajectories.to_list_of_dicts(),
"screenshots": self.screenshots.to_list_of_dicts(),
}
return blackboard_dict
|
blackboard_to_json()
Convert the blackboard to a JSON string.
Source code in agents/memory/blackboard.py
| def blackboard_to_json(self) -> str:
"""
Convert the blackboard to a JSON string.
:return: The JSON string.
"""
return json.dumps(self.blackboard_to_dict())
|
blackboard_to_prompt()
Convert the blackboard to a prompt.
Source code in agents/memory/blackboard.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 | def blackboard_to_prompt(self) -> List[str]:
"""
Convert the blackboard to a prompt.
:return: The prompt.
"""
prefix = [
{
"type": "text",
"text": "[Blackboard:]",
}
]
blackboard_prompt = (
prefix
+ self.texts_to_prompt(self.questions, "[Questions & Answers:]")
+ self.texts_to_prompt(self.requests, "[Request History:]")
+ self.texts_to_prompt(
self.trajectories, "[Step Trajectories Completed Previously:]"
)
+ self.screenshots_to_prompt()
)
return blackboard_prompt
|
clear()
Clear the blackboard.
Source code in agents/memory/blackboard.py
310
311
312
313
314
315
316
317 | def clear(self) -> None:
"""
Clear the blackboard.
"""
self.questions.clear()
self.requests.clear()
self.trajectories.clear()
self.screenshots.clear()
|
is_empty()
Check if the blackboard is empty.
| Returns: |
-
bool
–
True if the blackboard is empty, False otherwise.
|
Source code in agents/memory/blackboard.py
298
299
300
301
302
303
304
305
306
307
308 | def is_empty(self) -> bool:
"""
Check if the blackboard is empty.
:return: True if the blackboard is empty, False otherwise.
"""
return (
self.questions.is_empty()
and self.requests.is_empty()
and self.trajectories.is_empty()
and self.screenshots.is_empty()
)
|
load_questions(file_path, last_k=-1)
Load the data from a file.
| Parameters: |
-
file_path
(str)
–
-
last_k
–
The number of lines to read from the end of the file. If -1, read all lines.
|
Source code in agents/memory/blackboard.py
192
193
194
195
196
197
198
199
200 | def load_questions(self, file_path: str, last_k=-1) -> None:
"""
Load the data from a file.
:param file_path: The path of the file.
:param last_k: The number of lines to read from the end of the file. If -1, read all lines.
"""
qa_list = self.read_json_file(file_path, last_k)
for qa in qa_list:
self.add_questions(qa)
|
questions_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def questions_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.questions.to_json()
|
read_json_file(file_path, last_k=-1)
staticmethod
Read the json file.
| Parameters: |
-
file_path
(str)
–
-
last_k
–
The number of lines to read from the end of the file. If -1, read all lines.
|
Source code in agents/memory/blackboard.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 | @staticmethod
def read_json_file(file_path: str, last_k=-1) -> Dict[str, str]:
"""
Read the json file.
:param file_path: The path of the file.
:param last_k: The number of lines to read from the end of the file. If -1, read all lines.
:return: The data in the file.
"""
data_list = []
# Check if the file exists
if os.path.exists(file_path):
# Open the file and read the lines
with open(file_path, "r", encoding="utf-8") as file:
lines = file.readlines()
# If last_k is not -1, only read the last k lines
if last_k != -1:
lines = lines[-last_k:]
# Parse the lines as JSON
for line in lines:
try:
data = json.loads(line.strip())
data_list.append(data)
except json.JSONDecodeError:
print(f"Warning: Unable to parse line as JSON: {line}")
return data_list
|
requests_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def requests_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.requests.to_json()
|
screenshots_to_json()
Convert the images to a dictionary.
| Returns: |
-
str
–
The images in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def screenshots_to_json(self) -> str:
"""
Convert the images to a dictionary.
:return: The images in the dictionary format.
"""
return self.screenshots.to_json()
|
screenshots_to_prompt()
Convert the images to a prompt.
Source code in agents/memory/blackboard.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 | def screenshots_to_prompt(self) -> List[str]:
"""
Convert the images to a prompt.
:return: The prompt.
"""
user_content = []
for screenshot_dict in self.screenshots.list_content:
user_content.append(
{
"type": "text",
"text": json.dumps(
screenshot_dict.get(ImageMemoryItemNames.METADATA, "")
),
}
)
user_content.append(
{
"type": "image_url",
"image_url": {
"url": screenshot_dict.get(ImageMemoryItemNames.IMAGE_STR, "")
},
}
)
return user_content
|
texts_to_prompt(memory, prefix)
Convert the data to a prompt.
Source code in agents/memory/blackboard.py
202
203
204
205
206
207
208
209
210
211
212 | def texts_to_prompt(self, memory: Memory, prefix: str) -> List[str]:
"""
Convert the data to a prompt.
:return: The prompt.
"""
user_content = [
{"type": "text", "text": f"{prefix}\n {json.dumps(memory.list_content)}"}
]
return user_content
|
trajectories_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def trajectories_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.trajectories.to_json()
|
Key Methods
| Method |
Purpose |
Example Usage |
add_questions(item) |
Add Q&A with user |
Store user clarification dialogs |
add_requests(item) |
Add user request |
Track historical user requests |
add_trajectories(item) |
Add execution step |
Record agent actions and decisions |
add_image(path, metadata) |
Add screenshot |
Save important UI states |
blackboard_to_prompt() |
Convert to LLM prompt |
Build context for agent inference |
blackboard_to_dict() |
Export as dictionary |
Serialize for persistence |
blackboard_from_dict(data) |
Import from dictionary |
Restore from persistence |
clear() |
Reset blackboard |
New session initialization |
is_empty() |
Check if empty |
Validate blackboard state |
Example: Multi-Agent Coordination via Blackboard
from ufo.agents.memory.blackboard import Blackboard
# Initialize shared blackboard
blackboard = Blackboard()
# HostAgent adds user request to blackboard
class HostAgent:
def handle(self, context):
# ... process user request ...
user_request = "Create a presentation about AI"
# Write to blackboard for AppAgent to read
blackboard.add_requests({"request": user_request, "timestamp": "2025-11-12"})
blackboard.add_trajectories({
"step": 1,
"agent": "HostAgent",
"action": "delegate_task",
"app": "PowerPoint"
})
# Delegate to AppAgent
return AgentStatus.CONTINUE, AppAgent
# AppAgent reads from blackboard and performs task
class AppAgent:
def handle(self, context):
# Read from blackboard
recent_requests = blackboard.requests.content
if recent_requests:
last_request = recent_requests[-1]
print(f"AppAgent working on: {last_request.get_value('request')}")
# ... perform actions ...
# Write task result back to blackboard
blackboard.add_trajectories({
"step": 2,
"agent": "AppAgent",
"action": "create_presentation",
"status": "completed"
})
return AgentStatus.FINISH, None
Blackboard Persistence
The Blackboard supports serialization for session recovery:
graph LR
subgraph "Session Lifecycle"
Start[Session Start]
Execute[Agent Execution]
End[Session End]
Start --> Execute
Execute --> End
end
subgraph "Serialization"
Dict[blackboard_to_dict]
JSON[JSON Format]
end
Execute --> Dict
Dict --> JSON
style Execute fill:#ffe1e1
Example: Blackboard Serialization
from ufo.agents.memory.blackboard import Blackboard
import json
# Create and use blackboard
blackboard = Blackboard()
blackboard.add_requests({"request": "Create chart", "priority": "high"})
blackboard.add_trajectories({"step": 1, "action": "open_excel"})
# Serialize to dictionary
blackboard_dict = blackboard.blackboard_to_dict()
# Save to file
with open("blackboard_state.json", "w") as f:
json.dump(blackboard_dict, f)
# Later, restore from file
new_blackboard = Blackboard()
with open("blackboard_state.json", "r") as f:
loaded_dict = json.load(f)
new_blackboard.blackboard_from_dict(loaded_dict)
Memory Types and Usage Patterns
The Memory System supports different types of information storage based on use cases:
| Memory Type |
Storage Location |
Persistence |
Access Pattern |
Primary Use Case |
| Execution History |
Memory (agent-specific) |
Session lifetime |
Sequential, recent-first |
LLM context, debugging |
| Shared State |
Blackboard |
File-backed |
Key-value lookup |
Multi-agent coordination |
| Session Context |
Blackboard |
File-backed |
Hierarchical access |
Session recovery, checkpoints |
| Global Trajectories |
Blackboard |
JSONL append |
Sequential log |
Audit trail, analytics |
Common Memory Patterns
Pattern 1: Recent Context for LLM Prompts
# Use Memory.content property to get recent execution context
all_items = agent.memory.content
recent_steps = all_items[-5:] if len(all_items) > 5 else all_items
prompt_context = "\n".join([
f"Step {item.get_value('step')}: {item.get_value('action')}"
for item in recent_steps
])
# HostAgent writes to Blackboard
blackboard.add_requests({"request": "Create Excel chart", "app": "Excel"})
# AppAgent reads from Blackboard
requests = blackboard.requests.content
if requests:
latest_request = requests[-1]
app = latest_request.get_value("app")
Pattern 3: Execution History Tracking
# Record each step in trajectories
blackboard.add_trajectories({
"step": 1,
"agent": "AppAgent",
"action": "click_button",
"target": "Save",
"status": "success"
})
# Later, review execution history
history = blackboard.trajectories.content
for item in history:
print(f"Step {item.get_value('step')}: {item.get_value('action')}")
Pattern 4: Screenshot Memory
# Save important UI state with metadata
blackboard.add_image(
screenshot_path="screenshots/step_5.png",
metadata={"step": 5, "description": "Before form submission"}
)
# Access screenshots for review
screenshots = blackboard.screenshots.content
for screenshot in screenshots:
metadata = screenshot.get_value("metadata")
path = screenshot.get_value("image_path")
Integration with Agent Architecture
The Memory System integrates with all three architectural layers:
graph TB
subgraph "Level-1: State Layer"
State[AgentState.handle]
end
subgraph "Level-2: Strategy Layer"
DataCollection[DATA_COLLECTION<br/>Strategy]
LLMInteraction[LLM_INTERACTION<br/>Strategy]
ActionExecution[ACTION_EXECUTION<br/>Strategy]
MemoryUpdate[MEMORY_UPDATE<br/>Strategy]
end
subgraph "Memory System"
Memory[Memory<br/>Short-term]
Blackboard[Blackboard<br/>Long-term]
end
State --> DataCollection
DataCollection --> LLMInteraction
LLMInteraction --> ActionExecution
ActionExecution --> MemoryUpdate
MemoryUpdate -->|Write| Memory
LLMInteraction -.Read Context.-> Memory
State -.Read/Write.-> Blackboard
MemoryUpdate -.Write Trajectories.-> Blackboard
style Memory fill:#e1f5ff
style Blackboard fill:#fff4e1
style MemoryUpdate fill:#ffe1e1
Integration Points
| Component |
Interaction with Memory |
Interaction with Blackboard |
| AgentState.handle() |
- |
Read shared state, write delegation info |
| DATA_COLLECTION Strategy |
Read recent steps for context |
- |
| LLM_INTERACTION Strategy |
Read history for prompt building |
- |
| ACTION_EXECUTION Strategy |
- |
- |
| MEMORY_UPDATE Strategy |
Write MemoryItem after each step |
Write execution trajectories |
| ProcessorTemplate |
Maintain agent-specific Memory instance |
Access shared Blackboard instance |
Memory vs Blackboard Decision Guide:
Use Memory when:
- Information is agent-specific (execution history)
- Data is only needed during current session
- Building LLM prompts with recent context
- Tracking agent's own performance
Use Blackboard when:
- Information needs to be shared across agents
- Data should persist across session restarts
- Coordinating multi-agent workflows
- Implementing handoffs between agents
- Storing global task state
Best Practices
Memory Management
Limit Memory Size:
# Prevent unbounded memory growth
class Memory:
MAX_ITEMS = 100
def add_memory_item(self, item):
self._content.append(item)
if len(self._content) > self.MAX_ITEMS:
self._content = self._content[-self.MAX_ITEMS:] # Keep latest 100
Selective Context for LLM:
# Don't send full MemoryItem objects to LLM
def build_prompt_context(memory):
all_items = memory.content
recent = all_items[-5:] if len(all_items) > 5 else all_items
return "\n".join([
f"Step {item.get_value('step')}: {item.get_value('action')} -> "
f"{item.get_value('status')}"
for item in recent
])
Avoid Storing Large Binary Data:
Store file paths instead of file contents in MemoryItem:
# Good: Store path
memory_item.set_value("screenshot", "screenshots/step_3.png")
# Bad: Store binary data
# memory_item.set_value("screenshot", <binary image data>)
Blackboard Management
Organize with Descriptive Keys:
# Use descriptive keys in MemoryItem dictionaries
blackboard.add_trajectories({
"step": 1,
"agent": "HostAgent",
"action": "select_app",
"app_name": "Word",
"timestamp": "2025-11-12T10:00:00"
})
Regular Serialization:
# Periodically save blackboard state
class Session:
def __init__(self):
self.blackboard = Blackboard()
self.save_interval = 10 # Every 10 steps
def execute_step(self, step_num):
# ... execute step ...
if step_num % self.save_interval == 0:
state = self.blackboard.blackboard_to_dict()
with open("blackboard_backup.json", "w") as f:
json.dump(state, f)
Clean Up Appropriately:
# Clear blackboard when starting new session
if new_session:
blackboard.clear()
Common Pitfalls
Pitfall 1: Confusing Memory and Blackboard Scope
Problem: Storing agent-specific data in Blackboard or shared data in Memory.
Solution: Follow the scope principle:
- Memory = agent-specific, session-lifetime
- Blackboard = multi-agent shared, persistent
# Correct
agent.memory.add_memory_item(...) # Agent's own history
blackboard.add_trajectories({...}) # Shared execution history
Pitfall 2: Memory Leaks in Long Sessions
Problem: Memory grows unbounded in long-running sessions.
Solution: Implement memory size limits or periodic cleanup:
# Add size limit
if len(memory.content) > 1000:
memory._content = memory.content[-500:] # Keep recent half
Pitfall 3: Not Preserving Important State
Problem: Losing important state during crashes.
Solution: Periodically serialize critical Blackboard state:
# After critical operations
state = blackboard.blackboard_to_dict()
with open("checkpoint.json", "w") as f:
json.dump(state, f)
API Reference
For complete API documentation, see:
This data class represents a memory of an agent.
content
property
Get the content of the memory.
length
property
Get the length of the memory.
| Returns: |
-
int
–
The length of the memory.
|
list_content
property
List the content of the memory.
| Returns: |
-
List[Dict[str, str]]
–
The content of the memory.
|
add_memory_item(memory_item)
Add a memory item to the memory.
Source code in agents/memory/memory.py
| def add_memory_item(self, memory_item: MemoryItem) -> None:
"""
Add a memory item to the memory.
:param memory_item: The memory item to add.
"""
self._content.append(memory_item)
|
clear()
Clear the memory.
Source code in agents/memory/memory.py
| def clear(self) -> None:
"""
Clear the memory.
"""
self._content = []
|
delete_memory_item(step)
Delete a memory item from the memory.
| Parameters: |
-
step
(int)
–
The step of the memory item to delete.
|
Source code in agents/memory/memory.py
| def delete_memory_item(self, step: int) -> None:
"""
Delete a memory item from the memory.
:param step: The step of the memory item to delete.
"""
self._content = [item for item in self._content if item.step != step]
|
filter_memory_from_keys(keys)
Filter the memory from the keys. If an item does not have the key, the key will be ignored.
Source code in agents/memory/memory.py
123
124
125
126
127
128
129 | def filter_memory_from_keys(self, keys: List[str]) -> List[Dict[str, str]]:
"""
Filter the memory from the keys. If an item does not have the key, the key will be ignored.
:param keys: The keys to filter.
:return: The filtered memory.
"""
return [item.filter(keys) for item in self._content]
|
filter_memory_from_steps(steps)
Filter the memory from the steps.
Source code in agents/memory/memory.py
115
116
117
118
119
120
121 | def filter_memory_from_steps(self, steps: List[int]) -> List[Dict[str, str]]:
"""
Filter the memory from the steps.
:param steps: The steps to filter.
:return: The filtered memory.
"""
return [item.to_dict() for item in self._content if item.step in steps]
|
from_list_of_dicts(data)
Convert the list of dictionaries to the memory.
| Parameters: |
-
data
(List[Dict[str, str]])
–
The list of dictionaries.
|
Source code in agents/memory/memory.py
176
177
178
179
180
181
182
183
184
185 | def from_list_of_dicts(self, data: List[Dict[str, str]]) -> None:
"""
Convert the list of dictionaries to the memory.
:param data: The list of dictionaries.
"""
self._content = []
for item in data:
memory_item = MemoryItem()
memory_item.from_dict(item)
self._content.append(memory_item)
|
get_latest_item()
Get the latest memory item.
Source code in agents/memory/memory.py
187
188
189
190
191
192
193
194 | def get_latest_item(self) -> MemoryItem:
"""
Get the latest memory item.
:return: The latest memory item.
"""
if self.length == 0:
return None
return self._content[-1]
|
is_empty()
Check if the memory is empty.
| Returns: |
-
bool
–
The boolean value indicating if the memory is empty.
|
Source code in agents/memory/memory.py
| def is_empty(self) -> bool:
"""
Check if the memory is empty.
:return: The boolean value indicating if the memory is empty.
"""
return self.length == 0
|
load(content)
Load the data from the memory.
Source code in agents/memory/memory.py
| def load(self, content: List[MemoryItem]) -> None:
"""
Load the data from the memory.
:param content: The content to load.
"""
self._content = content
|
to_json()
Convert the memory to a JSON string.
Source code in agents/memory/memory.py
159
160
161
162
163
164
165
166
167 | def to_json(self) -> str:
"""
Convert the memory to a JSON string.
:return: The JSON string.
"""
return json.dumps(
[item.to_dict() for item in self._content if item is not None]
)
|
to_list_of_dicts()
Convert the memory to a list of dictionaries.
| Returns: |
-
List[Dict[str, str]]
–
The list of dictionaries.
|
Source code in agents/memory/memory.py
| def to_list_of_dicts(self) -> List[Dict[str, str]]:
"""
Convert the memory to a list of dictionaries.
:return: The list of dictionaries.
"""
return [item.to_dict() for item in self._content]
|
This data class represents a memory item of an agent at one step.
attributes
property
Get the attributes of the memory item.
add_values_from_dict(values)
Add fields to the memory item.
| Parameters: |
-
values
(Dict[str, Any])
–
The values of the fields.
|
Source code in agents/memory/memory.py
| def add_values_from_dict(self, values: Dict[str, Any]) -> None:
"""
Add fields to the memory item.
:param values: The values of the fields.
"""
for key, value in values.items():
self.set_value(key, value)
|
filter(keys=[])
Fetch the memory item.
| Parameters: |
-
keys
(List[str], default:
[]
)
–
|
| Returns: |
-
None
–
The filtered memory item.
|
Source code in agents/memory/memory.py
| def filter(self, keys: List[str] = []) -> None:
"""
Fetch the memory item.
:param keys: The keys to fetch.
:return: The filtered memory item.
"""
return {key: value for key, value in self.to_dict().items() if key in keys}
|
from_dict(data)
Convert the dictionary to a MemoryItem.
Source code in agents/memory/memory.py
| def from_dict(self, data: Dict[str, str]) -> None:
"""
Convert the dictionary to a MemoryItem.
:param data: The dictionary.
"""
for key, value in data.items():
self.set_value(key, value)
|
get_value(key)
Get the value of the field.
Source code in agents/memory/memory.py
| def get_value(self, key: str) -> Optional[str]:
"""
Get the value of the field.
:param key: The key of the field.
:return: The value of the field.
"""
return getattr(self, key, None)
|
get_values(keys)
Get the values of the fields.
| Returns: |
-
dict
–
The values of the fields.
|
Source code in agents/memory/memory.py
| def get_values(self, keys: List[str]) -> dict:
"""
Get the values of the fields.
:param keys: The keys of the fields.
:return: The values of the fields.
"""
return {key: self.get_value(key) for key in keys}
|
set_value(key, value)
Add a field to the memory item.
| Parameters: |
-
key
(str)
–
-
value
(str)
–
|
Source code in agents/memory/memory.py
55
56
57
58
59
60
61
62
63
64 | def set_value(self, key: str, value: str) -> None:
"""
Add a field to the memory item.
:param key: The key of the field.
:param value: The value of the field.
"""
setattr(self, key, value)
if key not in self._memory_attributes:
self._memory_attributes.append(key)
|
to_dict()
Convert the MemoryItem to a dictionary.
Source code in agents/memory/memory.py
19
20
21
22
23
24
25
26
27
28
29 | def to_dict(self) -> Dict[str, str]:
"""
Convert the MemoryItem to a dictionary.
:return: The dictionary.
"""
return {
key: value
for key, value in self.__dict__.items()
if key in self._memory_attributes
}
|
to_json()
Convert the memory item to a JSON string.
Source code in agents/memory/memory.py
| def to_json(self) -> str:
"""
Convert the memory item to a JSON string.
:return: The JSON string.
"""
return json.dumps(self.to_dict())
|
Class for the blackboard, which stores the data and images which are visible to all the agents.
Initialize the blackboard.
Source code in agents/memory/blackboard.py
41
42
43
44
45
46
47
48
49
50
51
52
53 | def __init__(self) -> None:
"""
Initialize the blackboard.
"""
self._questions: Memory = Memory()
self._requests: Memory = Memory()
self._trajectories: Memory = Memory()
self._screenshots: Memory = Memory()
if ufo_config.system.use_customization:
self.load_questions(
ufo_config.system.qa_pair_file, ufo_config.system.qa_pair_num
)
|
questions
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The questions from the blackboard.
|
requests
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The requests from the blackboard.
|
screenshots
property
Get the images from the blackboard.
| Returns: |
-
Memory
–
The images from the blackboard.
|
trajectories
property
Get the data from the blackboard.
| Returns: |
-
Memory
–
The trajectories from the blackboard.
|
add_data(data, memory)
Add the data to the a memory in the blackboard.
| Parameters: |
-
data
(Union[MemoryItem, Dict[str, str], str])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
-
memory
(Memory)
–
The memory to add the data to.
|
Source code in agents/memory/blackboard.py
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107 | def add_data(
self, data: Union[MemoryItem, Dict[str, str], str], memory: Memory
) -> None:
"""
Add the data to the a memory in the blackboard.
:param data: The data to be added. It can be a dictionary or a MemoryItem or a string.
:param memory: The memory to add the data to.
"""
if isinstance(data, dict):
data_memory = MemoryItem()
data_memory.add_values_from_dict(data)
memory.add_memory_item(data_memory)
elif isinstance(data, MemoryItem):
memory.add_memory_item(data)
elif isinstance(data, str):
data_memory = MemoryItem()
data_memory.add_values_from_dict({"text": data})
memory.add_memory_item(data_memory)
else:
print(f"Warning: Unsupported data type: {type(data)} when adding data.")
|
add_image(screenshot_path='', metadata=None)
Add the image to the blackboard.
| Parameters: |
-
screenshot_path
(str, default:
''
)
–
-
metadata
(Optional[Dict[str, str]], default:
None
)
–
The metadata of the image.
|
Source code in agents/memory/blackboard.py
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162 | def add_image(
self,
screenshot_path: str = "",
metadata: Optional[Dict[str, str]] = None,
) -> None:
"""
Add the image to the blackboard.
:param screenshot_path: The path of the image.
:param metadata: The metadata of the image.
"""
if os.path.exists(screenshot_path):
screenshot_str = utils.encode_image_from_path(screenshot_path)
else:
print(f"Screenshot path {screenshot_path} does not exist.")
screenshot_str = ""
image_memory_item = ImageMemoryItem()
image_memory_item.add_values_from_dict(
{
ImageMemoryItemNames.METADATA: metadata.get(
ImageMemoryItemNames.METADATA
),
ImageMemoryItemNames.IMAGE_PATH: screenshot_path,
ImageMemoryItemNames.IMAGE_STR: screenshot_str,
}
)
self.screenshots.add_memory_item(image_memory_item)
|
add_questions(questions)
Add the data to the blackboard.
| Parameters: |
-
questions
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
109
110
111
112
113
114
115 | def add_questions(self, questions: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param questions: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(questions, self.questions)
|
add_requests(requests)
Add the data to the blackboard.
| Parameters: |
-
requests
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
117
118
119
120
121
122
123 | def add_requests(self, requests: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param requests: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(requests, self.requests)
|
add_trajectories(trajectories)
Add the data to the blackboard.
| Parameters: |
-
trajectories
(Union[MemoryItem, Dict[str, str]])
–
The data to be added. It can be a dictionary or a MemoryItem or a string.
|
Source code in agents/memory/blackboard.py
125
126
127
128
129
130
131 | def add_trajectories(self, trajectories: Union[MemoryItem, Dict[str, str]]) -> None:
"""
Add the data to the blackboard.
:param trajectories: The data to be added. It can be a dictionary or a MemoryItem or a string.
"""
self.add_data(trajectories, self.trajectories)
|
blackboard_from_dict(blackboard_dict)
Convert the dictionary to the blackboard.
| Parameters: |
-
blackboard_dict
(Dict[str, List[Dict[str, str]]])
–
|
Source code in agents/memory/blackboard.py
262
263
264
265
266
267
268
269
270
271
272 | def blackboard_from_dict(
self, blackboard_dict: Dict[str, List[Dict[str, str]]]
) -> None:
"""
Convert the dictionary to the blackboard.
:param blackboard_dict: The dictionary.
"""
self.questions.from_list_of_dicts(blackboard_dict.get("questions", []))
self.requests.from_list_of_dicts(blackboard_dict.get("requests", []))
self.trajectories.from_list_of_dicts(blackboard_dict.get("trajectories", []))
self.screenshots.from_list_of_dicts(blackboard_dict.get("screenshots", []))
|
blackboard_to_dict()
Convert the blackboard to a dictionary.
| Returns: |
-
Dict[str, List[Dict[str, str]]]
–
The blackboard in the dictionary format.
|
Source code in agents/memory/blackboard.py
241
242
243
244
245
246
247
248
249
250
251
252
253 | def blackboard_to_dict(self) -> Dict[str, List[Dict[str, str]]]:
"""
Convert the blackboard to a dictionary.
:return: The blackboard in the dictionary format.
"""
blackboard_dict = {
"questions": self.questions.to_list_of_dicts(),
"requests": self.requests.to_list_of_dicts(),
"trajectories": self.trajectories.to_list_of_dicts(),
"screenshots": self.screenshots.to_list_of_dicts(),
}
return blackboard_dict
|
blackboard_to_json()
Convert the blackboard to a JSON string.
Source code in agents/memory/blackboard.py
| def blackboard_to_json(self) -> str:
"""
Convert the blackboard to a JSON string.
:return: The JSON string.
"""
return json.dumps(self.blackboard_to_dict())
|
blackboard_to_prompt()
Convert the blackboard to a prompt.
Source code in agents/memory/blackboard.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296 | def blackboard_to_prompt(self) -> List[str]:
"""
Convert the blackboard to a prompt.
:return: The prompt.
"""
prefix = [
{
"type": "text",
"text": "[Blackboard:]",
}
]
blackboard_prompt = (
prefix
+ self.texts_to_prompt(self.questions, "[Questions & Answers:]")
+ self.texts_to_prompt(self.requests, "[Request History:]")
+ self.texts_to_prompt(
self.trajectories, "[Step Trajectories Completed Previously:]"
)
+ self.screenshots_to_prompt()
)
return blackboard_prompt
|
clear()
Clear the blackboard.
Source code in agents/memory/blackboard.py
310
311
312
313
314
315
316
317 | def clear(self) -> None:
"""
Clear the blackboard.
"""
self.questions.clear()
self.requests.clear()
self.trajectories.clear()
self.screenshots.clear()
|
is_empty()
Check if the blackboard is empty.
| Returns: |
-
bool
–
True if the blackboard is empty, False otherwise.
|
Source code in agents/memory/blackboard.py
298
299
300
301
302
303
304
305
306
307
308 | def is_empty(self) -> bool:
"""
Check if the blackboard is empty.
:return: True if the blackboard is empty, False otherwise.
"""
return (
self.questions.is_empty()
and self.requests.is_empty()
and self.trajectories.is_empty()
and self.screenshots.is_empty()
)
|
load_questions(file_path, last_k=-1)
Load the data from a file.
| Parameters: |
-
file_path
(str)
–
-
last_k
–
The number of lines to read from the end of the file. If -1, read all lines.
|
Source code in agents/memory/blackboard.py
192
193
194
195
196
197
198
199
200 | def load_questions(self, file_path: str, last_k=-1) -> None:
"""
Load the data from a file.
:param file_path: The path of the file.
:param last_k: The number of lines to read from the end of the file. If -1, read all lines.
"""
qa_list = self.read_json_file(file_path, last_k)
for qa in qa_list:
self.add_questions(qa)
|
questions_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def questions_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.questions.to_json()
|
read_json_file(file_path, last_k=-1)
staticmethod
Read the json file.
| Parameters: |
-
file_path
(str)
–
-
last_k
–
The number of lines to read from the end of the file. If -1, read all lines.
|
Source code in agents/memory/blackboard.py
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348 | @staticmethod
def read_json_file(file_path: str, last_k=-1) -> Dict[str, str]:
"""
Read the json file.
:param file_path: The path of the file.
:param last_k: The number of lines to read from the end of the file. If -1, read all lines.
:return: The data in the file.
"""
data_list = []
# Check if the file exists
if os.path.exists(file_path):
# Open the file and read the lines
with open(file_path, "r", encoding="utf-8") as file:
lines = file.readlines()
# If last_k is not -1, only read the last k lines
if last_k != -1:
lines = lines[-last_k:]
# Parse the lines as JSON
for line in lines:
try:
data = json.loads(line.strip())
data_list.append(data)
except json.JSONDecodeError:
print(f"Warning: Unable to parse line as JSON: {line}")
return data_list
|
requests_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def requests_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.requests.to_json()
|
screenshots_to_json()
Convert the images to a dictionary.
| Returns: |
-
str
–
The images in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def screenshots_to_json(self) -> str:
"""
Convert the images to a dictionary.
:return: The images in the dictionary format.
"""
return self.screenshots.to_json()
|
screenshots_to_prompt()
Convert the images to a prompt.
Source code in agents/memory/blackboard.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 | def screenshots_to_prompt(self) -> List[str]:
"""
Convert the images to a prompt.
:return: The prompt.
"""
user_content = []
for screenshot_dict in self.screenshots.list_content:
user_content.append(
{
"type": "text",
"text": json.dumps(
screenshot_dict.get(ImageMemoryItemNames.METADATA, "")
),
}
)
user_content.append(
{
"type": "image_url",
"image_url": {
"url": screenshot_dict.get(ImageMemoryItemNames.IMAGE_STR, "")
},
}
)
return user_content
|
texts_to_prompt(memory, prefix)
Convert the data to a prompt.
Source code in agents/memory/blackboard.py
202
203
204
205
206
207
208
209
210
211
212 | def texts_to_prompt(self, memory: Memory, prefix: str) -> List[str]:
"""
Convert the data to a prompt.
:return: The prompt.
"""
user_content = [
{"type": "text", "text": f"{prefix}\n {json.dumps(memory.list_content)}"}
]
return user_content
|
trajectories_to_json()
Convert the data to a dictionary.
| Returns: |
-
str
–
The data in the dictionary format.
|
Source code in agents/memory/blackboard.py
| def trajectories_to_json(self) -> str:
"""
Convert the data to a dictionary.
:return: The data in the dictionary format.
"""
return self.trajectories.to_json()
|
Summary
Key Takeaways:
- Dual-Memory Architecture: Memory (short-term, agent-specific) + Blackboard (long-term, shared)
- Memory for Execution History: Stores chronological MemoryItem instances for LLM context and debugging
- Blackboard for Coordination: Implements Blackboard Pattern for multi-agent communication
- Flexible Schema: MemoryItem supports custom fields for platform-specific requirements
- Persistence Support: Blackboard can serialize/deserialize via dictionaries for session recovery
- Integration: MEMORY_UPDATE strategy writes to Memory, states coordinate via Blackboard
- Best Practices: Limit memory size, organize Blackboard with descriptive keys, periodically serialize state
- Scope Awareness: Use Memory for agent-specific data, Blackboard for shared coordination
The Memory System provides the foundation for both individual agent intelligence (through execution history) and collective multi-agent coordination (through shared knowledge space), enabling UFO3 to orchestrate complex cross-device tasks effectively.