Skip to content

Commit 4c4f482

Browse files
committed
Refactor memory action management and enhance document event handling
- Introduced `apply_actions_to_text` function to streamline action application on memory text. - Updated `MemoryActionManager` to utilize the new function for applying actions. - Enhanced `CollectionMemoryManager` to read memory text safely with `read_memory_text_or_empty`. - Added `apply_update_to_text` function for handling memory updates based on proposals. - Expanded `WebhookEventModel` to include `DocumentEventModel` for better event handling. - Implemented document event handling in `processor_request.py` to refresh memory on document updates and deletes. - Updated tests to verify memory handling on document events and ensure proper memory state management. - Modified `CollectionWorkspaceManager` to bootstrap only system prompts until memory is needed, improving initialization efficiency.
1 parent a1f1ddb commit 4c4f482

25 files changed

Lines changed: 945 additions & 83 deletions

.github/workflows/build-and-publish-container-image.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ on:
44
push:
55
tags:
66
- 'v*'
7+
release:
8+
types:
9+
- published
710
workflow_dispatch:
811

912
env:
@@ -54,7 +57,7 @@ jobs:
5457
cache-to: type=gha,mode=max
5558

5659
- name: Create GitHub Release
57-
if: startsWith(github.ref, 'refs/tags/v')
60+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
5861
uses: softprops/action-gh-release@v2
5962
with:
6063
generate_release_notes: true

config.example.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ features:
119119
runtime:
120120
# If true, plan and simulate side effects without actually mutating Outline state.
121121
dry_run: false
122+
# Title of the remote Outline document used for collection memory.
123+
collection_memory_document_title: MEMORY
122124
# Maximum number of plan/replan rounds for one request.
123125
tool_execution_max_rounds: 10
124126
# Maximum number of steps the planner may propose in one round.

docker/config.yaml.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ features:
5959

6060
runtime:
6161
dry_run: false
62+
collection_memory_document_title: MEMORY
6263
tool_execution_max_rounds: 10
6364
tool_execution_max_steps: 6
6465
tool_execution_chunk_size: 2

docs/config.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ features:
9393

9494
runtime:
9595
dry_run: false
96+
collection_memory_document_title: MEMORY
9697
tool_execution_max_rounds: 10
9798
tool_execution_max_steps: 6
9899
tool_execution_chunk_size: 2
@@ -231,6 +232,7 @@ Packaged prompts live under `src/outline_agent/assets/prompts/`, while user over
231232
| Field | Required | Default | Purpose | Notes |
232233
| --- | --- | --- | --- | --- |
233234
| `runtime.dry_run` | No | `false` | Simulates side effects without mutating Outline state. | Useful for testing plans and prompts safely. |
235+
| `runtime.collection_memory_document_title` | No | `MEMORY` | Title of the remote Outline document used for collection-wide memory. | The runtime lazily pulls this document on the first involved chat after startup, mirrors it into local `memory/MEMORY.md`, and only creates it on the first actual memory write. |
234236
| `runtime.tool_execution_max_rounds` | No | `10` | Maximum number of planning/replanning rounds for one request. | Increase this for more complex tool-driven tasks. |
235237
| `runtime.tool_execution_max_steps` | No | `6` | Maximum number of steps the planner may propose in one round. | This is the planner budget, not the total global task budget. |
236238
| `runtime.tool_execution_chunk_size` | No | `2` | Number of planned steps actually executed before replanning. | Smaller chunks improve adaptability after partial failures. |

docs/workspace-memory-and-prompts.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ This document describes the current runtime storage model after the document-mem
99
- The only durable semantic memory layers are:
1010
- **collection memory**
1111
- **document memory**
12+
- Collection memory is **remote-first**:
13+
- the Outline document titled `MEMORY` (configurable) is the user-facing source of truth
14+
- `memory/MEMORY.md` is the local cache used for prompting and local inspection
1215
- Thread storage now keeps:
1316
- transcript
1417
- runtime state
@@ -22,6 +25,7 @@ For one collection, the workspace now looks like this:
2225
```text
2326
<workspace_root>/<collection_id>/
2427
memory/
28+
.collection.json
2529
00_SYSTEM.md
2630
MEMORY.md
2731
index.json
@@ -64,8 +68,9 @@ Purpose:
6468
Notes:
6569

6670
- `00_SYSTEM.md` is included into the final system prompt as collection-local instructions
67-
- `MEMORY.md` stores collection facts, decisions, and working notes
71+
- `MEMORY.md` is the local cache of the remote collection memory document
6872
- `workspace/` is where tools read/write files and where generated artifacts live before upload
73+
- the remote Outline document title defaults to `MEMORY` and can be changed with `runtime.collection_memory_document_title`
6974

7075
### 2. Document layer
7176

@@ -116,7 +121,9 @@ It is rebuilt from Outline comments on webhook events so deletes and edits are r
116121

117122
### Semantic memory truth
118123

119-
- collection-wide memory truth: `memory/MEMORY.md`
124+
- collection-wide memory truth:
125+
- remote source of truth: Outline document titled `MEMORY` by default
126+
- local cache: `memory/MEMORY.md`
120127
- document-wide memory truth: `documents/<document_id>/MEMORY.md`
121128

122129
These are model-maintained summaries, not raw transcript mirrors.
@@ -144,7 +151,17 @@ On a new comment:
144151
4. document comments are fetched
145152
5. thread transcript is rebuilt/synced into `comments.json`
146153
6. thread runtime state is refreshed from transcript
147-
7. if trigger conditions pass, the agent is invoked
154+
7. if trigger conditions pass, the runtime performs a one-time per-process pull/check for the collection memory document
155+
8. the agent is invoked
156+
157+
### `documents.create` / `documents.update`
158+
159+
If the affected document matches the configured collection memory document title:
160+
161+
1. only collections already involved by the agent in the current process are considered
162+
2. the runtime fetches the remote document body
163+
3. the local cache `memory/MEMORY.md` is created or replaced
164+
4. `memory/index.json` is regenerated
148165

149166
### `comments.update`
150167

@@ -199,6 +216,7 @@ Managed by:
199216

200217
- `src/outline_agent/managers/memory_manager.py`
201218
- `src/outline_agent/managers/memory_action_manager.py`
219+
- `src/outline_agent/managers/collection_memory_sync.py`
202220

203221
Two paths exist:
204222

@@ -208,8 +226,9 @@ If the user explicitly asks to remember / forget / correct collection memory:
208226

209227
1. router enables memory action path
210228
2. memory action manager proposes `add|update|delete|move`
211-
3. `memory/MEMORY.md` is edited directly
212-
4. `memory/index.json` is regenerated
229+
3. the remote collection memory document is created on demand or updated
230+
4. the local cache `memory/MEMORY.md` is refreshed
231+
5. `memory/index.json` is regenerated
213232

214233
This path is user-controlled and intentional.
215234

@@ -219,8 +238,9 @@ After a successful reply:
219238

220239
1. the collection memory updater reviews the latest interaction
221240
2. it proposes durable collection-level entries
222-
3. `memory/MEMORY.md` is appended/updated
223-
4. `memory/index.json` is regenerated
241+
3. the remote collection memory document is created on demand or updated
242+
4. the local cache `memory/MEMORY.md` is refreshed
243+
5. `memory/index.json` is regenerated
224244

225245
This should only retain reusable cross-document/cross-thread memory.
226246

@@ -328,7 +348,7 @@ These prompts still have important hard-coded protocol/skeleton pieces in code.
328348
| `src/outline_agent/planning/tool_planner.py::UNIFIED_TOOL_PLANNER_SYSTEM_PROMPT` | protocol prompt | tool planner protocol/schema; policy addendum now comes from the internal prompt policy file / override path |
329349
| `src/outline_agent/planning/tool_planner.py::_build_user_prompt` | user prompt template | provides tool catalog, work-dir inventory, prior rounds, document memory, attachments, and comment context |
330350
| `src/outline_agent/state/workspace.py::INITIAL_SYSTEM_TEMPLATE` | initialization template | bootstrap template for collection `memory/00_SYSTEM.md` |
331-
| `src/outline_agent/state/workspace.py::INITIAL_MEMORY_TEMPLATE` | initialization template | bootstrap template for collection `memory/MEMORY.md` |
351+
| `src/outline_agent/state/workspace.py::INITIAL_MEMORY_TEMPLATE` | initialization template | template used when collection memory is first created locally/remotely |
332352
| `src/outline_agent/state/workspace.py::INITIAL_DOCUMENT_MEMORY_TEMPLATE` | initialization template | bootstrap template for document `documents/<document_id>/MEMORY.md` |
333353

334354
### What is not currently externalized

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "outline-agent"
7-
version = "0.2.2"
7+
version = "0.2.3"
88
description = "Webhook-driven Outline thread agent"
99
readme = "README.md"
1010
requires-python = ">=3.11"

src/outline_agent/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

33
__all__ = ["__version__"]
44

5-
__version__ = "0.2.2"
5+
__version__ = "0.2.3"

src/outline_agent/app.py

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,21 @@ async def lifespan(_: FastAPI):
7171
)
7272
elif settings.tool_use_enabled:
7373
logger.warning("ask_web_search is unavailable: {}", web_search_probe.reason or "unknown reason")
74+
outline_client = None
75+
store = None
76+
processor = None
77+
try:
78+
outline_client, store = build_request_resources(settings)
79+
processor = build_comment_processor(
80+
settings=settings,
81+
outline_client=outline_client,
82+
store=store,
83+
)
84+
except Exception:
85+
logger.exception("Failed to initialize shared Outline processor resources")
86+
app.state.outline_client = outline_client
87+
app.state.store = store
88+
app.state.processor = processor
7489
yield
7590

7691

@@ -189,6 +204,8 @@ async def outline_webhook(request: Request) -> JSONResponse:
189204
"comments.create",
190205
"comments.update",
191206
"comments.delete",
207+
"documents.create",
208+
"documents.update",
192209
"documents.delete",
193210
"collections.delete",
194211
}:
@@ -208,15 +225,17 @@ async def outline_webhook(request: Request) -> JSONResponse:
208225
except ValidationError as exc:
209226
raise HTTPException(status_code=400, detail=f"invalid webhook payload: {exc}") from exc
210227

211-
outline_client = None
212-
store = None
213228
try:
214-
outline_client, store = build_request_resources(settings)
215-
processor = build_comment_processor(
216-
settings=settings,
217-
outline_client=outline_client,
218-
store=store,
219-
)
229+
outline_client = getattr(app.state, "outline_client", None)
230+
store = getattr(app.state, "store", None)
231+
processor = getattr(app.state, "processor", None)
232+
if outline_client is None or store is None or processor is None:
233+
outline_client, store = build_request_resources(settings)
234+
processor = build_comment_processor(
235+
settings=settings,
236+
outline_client=outline_client,
237+
store=store,
238+
)
220239
result = await processor.handle(envelope)
221240
logger.info(
222241
"Webhook processed: action={}, reason={}, comment_id={}, document_id={}, collection_id={}",

src/outline_agent/clients/model_client.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,9 @@ async def _iter_sse_events(response: httpx.Response) -> AsyncIterator[tuple[str
545545
if line.startswith(":"):
546546
continue
547547
if line.startswith("event:"):
548+
if data_lines:
549+
yield current_event, "\n".join(data_lines)
550+
data_lines = []
548551
current_event = line[6:].strip() or None
549552
continue
550553
if line.startswith("data:"):

src/outline_agent/core/config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -493,6 +493,7 @@ def settings_customise_sources(
493493
thread_turn_max_chars: int = 280
494494
memory_update_max_entries: int = 3
495495
memory_update_entry_max_chars: int = 180
496+
collection_memory_document_title: str = "MEMORY"
496497
document_memory_update_enabled: bool = True
497498
document_memory_model_ref: str | None = None
498499
document_memory_summary_max_chars: int = 320

0 commit comments

Comments
 (0)