Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 3 additions & 3 deletions .blog/published/memory-architecture-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ The retrieval side has a similar gap. OpenClaw pulls relevant memory files back

Construct's memory retrieval addresses this with a three-layer pipeline: FTS5 full-text search for exact keyword matches, cosine similarity over vector embeddings for semantic associations (catching "restaurants" → "Nightshade" even without shared vocabulary), and knowledge graph traversal for relational connections (walking edges like `Alice -> [introduced to] -> Nightshade -> [is a] -> restaurant`). Each layer covers gaps the others miss.

On the writing side, Construct stores every raw message permanently in the database. Nothing is ever discarded. The observational memory pipeline doesn't replace stored messages; it replaces them *in the context window*. Once a chunk of conversation has been compressed into observations, the agent sees the condensed observations plus only the recent unobserved messages, rather than replaying the entire transcript. But the full history remains in SQLite, searchable and intact. The observations are a compression layer for context assembly, not a lossy replacement for storage.
On the writing side, Construct stores every raw message permanently in the database. Nothing is ever discarded. The observational memory pipeline doesn't replace stored messages; it replaces them _in the context window_. Once a chunk of conversation has been compressed into observations, the agent sees the condensed observations plus only the recent unobserved messages, rather than replaying the entire transcript. But the full history remains in SQLite, searchable and intact. The observations are a compression layer for context assembly, not a lossy replacement for storage.

This sidesteps OpenClaw's core failure mode entirely. There's no dependency on the model choosing to write things down. Every message is stored automatically, and the observation pipeline ensures the meaning of older conversations stays in the agent's working context even after the raw messages rotate out of the context window.

Expand Down Expand Up @@ -125,8 +125,8 @@ This means the memory system degrades gracefully. On day one, there are no obser

The two follow-up articles cover the implementation in detail:

The *Observer, Reflector, Graph* article digs into the writing side: how raw messages are compressed into observations, how observations are condensed across generations by the reflector, and how stored memories spawn a knowledge graph of entities and relationships.
The _Observer, Reflector, Graph_ article digs into the writing side: how raw messages are compressed into observations, how observations are condensed across generations by the reflector, and how stored memories spawn a knowledge graph of entities and relationships.

The *Three Ways to Find a Memory* article covers the reading side: the waterfall of FTS5 full-text search, cosine similarity over embeddings, and graph traversal that backs the `memory_recall` tool, and the passive auto-injection that runs before the agent even starts thinking.
The _Three Ways to Find a Memory_ article covers the reading side: the waterfall of FTS5 full-text search, cosine similarity over embeddings, and graph traversal that backs the `memory_recall` tool, and the passive auto-injection that runs before the agent even starts thinking.

Together, the two pipelines are what give Construct something closer to the memory structure of a person than a chatbot: compressed, semantically indexed, queryable by meaning, and always available without needing to be asked.
43 changes: 22 additions & 21 deletions .blog/published/observer-reflector-graph.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,14 @@ The observer is triggered post-response, non-blocking:

```typescript
// src/agent.ts
memoryManager.runObserver(conversationId)
memoryManager
.runObserver(conversationId)
.then((ran) => {
if (ran) {
return memoryManager.runReflector(conversationId)
return memoryManager.runReflector(conversationId);
}
})
.catch((err) => agentLog.error`Post-response observation failed: ${err}`)
.catch((err) => agentLog.error`Post-response observation failed: ${err}`);
```

It only runs when unobserved messages cross a token threshold (3,000 tokens estimated at 4 chars/token). Below that, it's free: no API call, no latency.
Expand Down Expand Up @@ -62,14 +63,14 @@ The result is a small set of typed observations:

```typescript
interface Observation {
id: string
conversation_id: string
content: string // "User has a dentist appointment March 5th at 9am"
priority: 'low' | 'medium' | 'high'
observation_date: string // when the thing happened, not when it was observed
generation: number // 0 = observer output, 1+ = survived reflector rounds
superseded_at: string | null
token_count: number
id: string;
conversation_id: string;
content: string; // "User has a dentist appointment March 5th at 9am"
priority: "low" | "medium" | "high";
observation_date: string; // when the thing happened, not when it was observed
generation: number; // 0 = observer output, 1+ = survived reflector rounds
superseded_at: string | null;
token_count: number;
}
```

Expand All @@ -94,10 +95,10 @@ The reflector receives observations with their IDs, and returns both new condens
```typescript
// src/memory/reflector.ts
// Validate superseded IDs -only allow IDs that were in the input
const inputIds = new Set(input.observations.map((o) => o.id))
const inputIds = new Set(input.observations.map((o) => o.id));
result.superseded_ids = result.superseded_ids.filter(
(id) => typeof id === 'string' && inputIds.has(id),
)
(id) => typeof id === "string" && inputIds.has(id),
);
```

You can't trust an LLM to return valid database IDs without validating them. The reflector is allowed to supersede anything it was given. Nothing more.
Expand All @@ -111,12 +112,12 @@ At the start of each conversation turn, the agent builds a context window:
```typescript
// src/agent.ts
const { observationsText, activeMessages, hasObservations } =
await memoryManager.buildContext(conversationId)
await memoryManager.buildContext(conversationId);

if (hasObservations) {
historyMessages = activeMessages // only unobserved messages
historyMessages = activeMessages; // only unobserved messages
} else {
historyMessages = await getRecentMessages(db, conversationId, 20)
historyMessages = await getRecentMessages(db, conversationId, 20);
}
```

Expand All @@ -126,10 +127,10 @@ Observations are rendered as a stable text block:
// src/memory/context.ts
export function renderObservations(observations: Observation[]): string {
const lines = observations.map((o) => {
const priority = o.priority === 'high' ? '!' : o.priority === 'low' ? '~' : '-'
return `${priority} [${o.observation_date}] ${o.content}`
})
return lines.join('\n')
const priority = o.priority === "high" ? "!" : o.priority === "low" ? "~" : "-";
return `${priority} [${o.observation_date}] ${o.content}`;
});
return lines.join("\n");
}
```

Expand Down
2 changes: 1 addition & 1 deletion .claude/agent-memory-local/blog-writer/MEMORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
- Notable: `generation` field tracks how many reflector rounds an observation has survived; SQLite rowid watermark trick for insertion-order safety

3. **Three Ways to Find a Memory: FTS5, Embeddings, and Graph Traversal in a Personal AI**
- Topic: The memory *retrieval* side — passive vs. active retrieval, FTS5/embedding waterfall, graph expansion
- Topic: The memory _retrieval_ side — passive vs. active retrieval, FTS5/embedding waterfall, graph expansion
- Key systems: `src/db/queries.ts` (`recallMemories`), `src/tools/core/memory-recall.ts`, `src/memory/graph/queries.ts`, `src/agent.ts` (passive injection), `src/system-prompt.ts` (preamble)
- Angle: Three search modes (FTS5, cosine similarity, graph traversal) combined in a single retrieval pipeline; passive auto-injection vs. active tool-invoked recall
- Notable: queryEmbedding is generated once and reused for both memory recall AND tool pack selection; matchType field (`fts5`/`embedding`/`graph`) lets agent reason about retrieval confidence
Expand Down
15 changes: 15 additions & 0 deletions .claude/agents/blog-writer.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ You will explore the codebase, identify an interesting aspect (a clever design p
### Step 1: Explore and Discover

Read through the codebase to understand the overall architecture and find something genuinely interesting to write about. Use file reading tools to examine:

- `src/agent.ts`: core agent factory and message processing
- `src/tools/`: tool implementations and patterns
- `src/extensions/`: the extension loading system
Expand All @@ -33,6 +34,7 @@ Don't just skim. Read the actual code. The best articles come from genuine under
**Update your agent memory** as you discover topics you've already written about. This builds up institutional knowledge across conversations. Write concise notes about what you found and which topics have been covered.

Examples of what to record:

- Topics already covered in previous blog articles
- Particularly interesting code patterns spotted for future articles
- Areas of the codebase that changed significantly since last explored
Expand All @@ -43,6 +45,7 @@ Before writing, check your memory for previously covered topics. If you find ove
### Step 3: Choose Your Angle

Great technical blog articles aren't just documentation. They have a **thesis**. Some angles that work well:

- "Why we chose X over Y": exploring a tradeoff
- "The pattern behind...": extracting a reusable insight from specific code
- "How X actually works": demystifying something that looks simple but is surprisingly deep
Expand All @@ -53,6 +56,7 @@ Great technical blog articles aren't just documentation. They have a **thesis**.
### Step 4: Write the Article

Your article should:

- **Open with a hook**: A question, a surprising fact, a relatable problem, or a bold claim. Never open with "In this article, I will..."
- **Include real code snippets**: Pull actual code from the codebase (trimmed for clarity). Don't write pseudocode when real code is more compelling.
- **Tell a story**: Even technical articles benefit from narrative structure: setup, tension, resolution.
Expand Down Expand Up @@ -83,13 +87,15 @@ Article body...
```

Additional formatting rules:

- Appropriate headers to break up sections
- Code blocks with language annotations
- No unnecessary preamble or meta-commentary about the writing process

## Quality Checks

Before finalizing, verify:

- [ ] The code snippets are accurate and pulled from the actual codebase
- [ ] The article has a clear thesis, not just a tour of features
- [ ] Technical claims are correct (re-read the relevant code if unsure)
Expand Down Expand Up @@ -117,40 +123,49 @@ You have a persistent Persistent Agent Memory directory at `.claude/agent-memory
As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes. If nothing is written yet, record what you learned.

Guidelines:

- `MEMORY.md` is always loaded into your system prompt. Lines after 200 will be truncated, so keep it concise
- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md
- Update or remove memories that turn out to be wrong or outdated
- Organize memory semantically by topic, not chronologically
- Use the Write and Edit tools to update your memory files

What to save:

- Stable patterns and conventions confirmed across multiple interactions
- Key architectural decisions, important file paths, and project structure
- User preferences for workflow, tools, and communication style
- Solutions to recurring problems and debugging insights

What NOT to save:

- Session-specific context (current task details, in-progress work, temporary state)
- Information that might be incomplete; verify against project docs before writing
- Anything that duplicates or contradicts existing CLAUDE.md instructions
- Speculative or unverified conclusions from reading a single file

Explicit user requests:

- When the user asks you to remember something across sessions (e.g., "always use bun", "never auto-commit"), save it. No need to wait for multiple interactions
- When the user asks to forget or stop remembering something, find and remove the relevant entries from your memory files
- Since this memory is local-scope (not checked into version control), tailor your memories to this project and machine

## Searching past context

When looking for past context:

1. Search topic files in your memory directory:

```
Grep with pattern="<search term>" path=".claude/agent-memory-local/blog-writer/" glob="*.md"
```

2. Session transcript logs (last resort, large files, slow):

```
Grep with pattern="<search term>" path=".claude/projects/-home-reed-Code-0xreed-nullclaw-ts/" glob="*.jsonl"
```

Use narrow search terms (error messages, file paths, function names) rather than broad keywords.

## MEMORY.md
Expand Down
109 changes: 0 additions & 109 deletions .claude/agents/tech-librarian.md

This file was deleted.

Loading