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.

Returns:
  • List[str]

    The attributes.

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
66
67
68
69
70
71
72
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: [] ) –

    The keys to fetch.

Returns:
  • None

    The filtered memory item.

Source code in agents/memory/memory.py
46
47
48
49
50
51
52
53
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.

Parameters:
  • data (Dict[str, str]) –

    The dictionary.

Source code in agents/memory/memory.py
31
32
33
34
35
36
37
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.

Parameters:
  • key (str) –

    The key of the field.

Returns:
  • Optional[str]

    The value of the field.

Source code in agents/memory/memory.py
74
75
76
77
78
79
80
81
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.

Parameters:
  • keys (List[str]) –

    The keys of the fields.

Returns:
  • dict

    The values of the fields.

Source code in agents/memory/memory.py
83
84
85
86
87
88
89
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) –

    The key of the field.

  • value (str) –

    The value of the field.

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.

Returns:
  • Dict[str, str]

    The 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.

Returns:
  • str

    The JSON string.

Source code in agents/memory/memory.py
39
40
41
42
43
44
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.

Returns:

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.

Parameters:
  • memory_item (MemoryItem) –

    The memory item to add.

Source code in agents/memory/memory.py
131
132
133
134
135
136
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
138
139
140
141
142
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
152
153
154
155
156
157
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.

Parameters:
  • keys (List[str]) –

    The keys to filter.

Returns:
  • List[Dict[str, str]]

    The filtered memory.

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.

Parameters:
  • steps (List[int]) –

    The steps to filter.

Returns:
  • List[Dict[str, str]]

    The filtered memory.

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.

Returns:
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
212
213
214
215
216
217
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.

Parameters:
  • content (List[MemoryItem]) –

    The content to load.

Source code in agents/memory/memory.py
108
109
110
111
112
113
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.

Returns:
  • str

    The 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
169
170
171
172
173
174
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: '' ) –

    The path of the image.

  • 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]]]) –

    The dictionary.

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.

Returns:
  • str

    The JSON string.

Source code in agents/memory/blackboard.py
255
256
257
258
259
260
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.

Returns:
  • List[str]

    The 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) –

    The path of the file.

  • 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
164
165
166
167
168
169
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) –

    The path of the file.

  • last_k

    The number of lines to read from the end of the file. If -1, read all lines.

Returns:
  • Dict[str, str]

    The data in the file.

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
171
172
173
174
175
176
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
185
186
187
188
189
190
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.

Returns:
  • List[str]

    The 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.

Returns:
  • List[str]

    The 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
178
179
180
181
182
183
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
])

Pattern 2: Multi-Agent Information Sharing

# 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.

Returns:

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.

Parameters:
  • memory_item (MemoryItem) –

    The memory item to add.

Source code in agents/memory/memory.py
131
132
133
134
135
136
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
138
139
140
141
142
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
152
153
154
155
156
157
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.

Parameters:
  • keys (List[str]) –

    The keys to filter.

Returns:
  • List[Dict[str, str]]

    The filtered memory.

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.

Parameters:
  • steps (List[int]) –

    The steps to filter.

Returns:
  • List[Dict[str, str]]

    The filtered memory.

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.

Returns:
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
212
213
214
215
216
217
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.

Parameters:
  • content (List[MemoryItem]) –

    The content to load.

Source code in agents/memory/memory.py
108
109
110
111
112
113
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.

Returns:
  • str

    The 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
169
170
171
172
173
174
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.

Returns:
  • List[str]

    The attributes.

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
66
67
68
69
70
71
72
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: [] ) –

    The keys to fetch.

Returns:
  • None

    The filtered memory item.

Source code in agents/memory/memory.py
46
47
48
49
50
51
52
53
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.

Parameters:
  • data (Dict[str, str]) –

    The dictionary.

Source code in agents/memory/memory.py
31
32
33
34
35
36
37
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.

Parameters:
  • key (str) –

    The key of the field.

Returns:
  • Optional[str]

    The value of the field.

Source code in agents/memory/memory.py
74
75
76
77
78
79
80
81
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.

Parameters:
  • keys (List[str]) –

    The keys of the fields.

Returns:
  • dict

    The values of the fields.

Source code in agents/memory/memory.py
83
84
85
86
87
88
89
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) –

    The key of the field.

  • value (str) –

    The value of the field.

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.

Returns:
  • Dict[str, str]

    The 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.

Returns:
  • str

    The JSON string.

Source code in agents/memory/memory.py
39
40
41
42
43
44
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: '' ) –

    The path of the image.

  • 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]]]) –

    The dictionary.

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.

Returns:
  • str

    The JSON string.

Source code in agents/memory/blackboard.py
255
256
257
258
259
260
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.

Returns:
  • List[str]

    The 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) –

    The path of the file.

  • 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
164
165
166
167
168
169
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) –

    The path of the file.

  • last_k

    The number of lines to read from the end of the file. If -1, read all lines.

Returns:
  • Dict[str, str]

    The data in the file.

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
171
172
173
174
175
176
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
185
186
187
188
189
190
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.

Returns:
  • List[str]

    The 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.

Returns:
  • List[str]

    The 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
178
179
180
181
182
183
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.