Skip to content

feat: system-level Memory updated notification (ARM-54) - #69

Open
stepandel wants to merge 1 commit into
masterfrom
stepanarsentjev/arm-54-memory-updates-are-not-obvious
Open

feat: system-level Memory updated notification (ARM-54)#69
stepandel wants to merge 1 commit into
masterfrom
stepanarsentjev/arm-54-memory-updates-are-not-obvious

Conversation

@stepandel

Copy link
Copy Markdown
Owner

Summary

  • Adds harness-level detection of brain file edits via tool event subscribers — notification is system-level, not LLM-generated, so it always fires
  • Slack: posts a Block Kit context block (:brain: *Memory updated*) after the response — subtle, pill-like treatment matching the ChatGPT gold standard
  • Linear: emits a non-ephemeral thought activity before the response

Changes

  • src/brain-notify.ts (new) — BrainEditTracker that hooks into tool_execution_start/end events, detects writes to {stateDir}/brain/, deduplicates by filename, and flushes per turn
  • src/slack/client.tspostContextInThread() for Block Kit context blocks
  • src/slack/handler.ts — wires tracker into all session paths (new thread, follow-up, snapshot restart, /workflow, crash recovery)
  • src/linear/runtime.ts — wires tracker into attachProgressStreaming and runTurn

Test plan

  • All 408 existing tests pass
  • Typecheck passes
  • Lint clean
  • Manual: trigger a brain file edit in Slack DM → verify context block appears after response
  • Manual: trigger a brain file edit in a channel thread → verify context block appears
  • Manual: verify no notification when no brain files are edited
  • Manual: verify Linear thought activity appears before response on brain edit

Closes ARM-54

🤖 Generated with Claude Code

Adds harness-level detection of brain file writes so users always see
when the agent updates memory — regardless of whether the LLM mentions
it. Slack gets a Block Kit context pill (like ChatGPT's "Memory updated");
Linear gets a non-ephemeral thought activity before the response.

Closes ARM-54
@linear

linear Bot commented Apr 2, 2026

Copy link
Copy Markdown
ARM-54 Memory updates are not obvious

CleanShot 2026-03-18 at 23.25.26@2x.png


Research Report

Summary

Memory updates in Vera are invisible to users — when the agent edits .vera/brain/ files, there's no dedicated UI indicator, no structured notification, and the agent's verbal acknowledgment is unreliable (as shown in the screenshot above). The harness has no system-level detection of brain file writes; the only notification mechanism is a prompt-level "soul clause" in memory.md that depends on LLM compliance. Industry leaders like ChatGPT use a deterministic, system-level "Memory updated" pill — Vera should do the same.

Key Findings

  1. Brain system architecture: Brain files are loaded via loadBrainFiles() in src/session/index.ts and concatenated into the system prompt for agents with brain: true in their frontmatter (currently only the slack agent). The order frontmatter field controls prompt positioning. Current files: REACTIONS.md (order 1), tools.md (order 2), memory.md (order 3).
  2. Memory is updated via raw edit/write tools: There is no dedicated memory tool. The write tool (src/tools/write.ts) and edit tool (src/tools/edit.ts) are generic file tools with no special handling for brain file paths. Neither tool emits any signal when a brain file is targeted. Tool results are plain text ("Created new file {path}" / "Overwrote {path}" for write; diff output for edit).
  3. Tigris sync already watches brain files: startTigrisSync() in src/storage/tigris.ts watches ["crons", "workflows", "brain", "skills"] directories with fs.watch() and syncs changes to S3/Tigris. There's already a file watcher on the brain directory — it could be extended to emit memory-update events, not just sync to storage.
  4. Slack handler processes tool events but doesn't filter by target: In src/slack/handler.ts, the session subscriber sees tool_execution_start and tool_execution_end events with ev.toolName and ev.args. The progress tracker shows tool names and truncated args in Slack's typing indicator ("is running edit: /workspace/.vera/brain/memory.md"). However, this is ephemeral status text that disappears when the bot replies — not a persistent notification.
  5. Reaction directives system exists: The agent can embed [react:emoji_name] directives in responses, which the harness parses and applies as Slack reactions. This pattern could be extended — e.g., a [memory_updated:summary] directive that the harness renders as a distinct Slack block. The infrastructure for parsing structured directives from agent output already exists in extractReactionDirectives().
  6. Linear handler has activity types: src/linear/activity.ts supports thought, action, response, error, and elicitation activity types. A memory_updated activity type could be added for Linear surfaces.
  7. Session event system: The pi-coding-agent SDK fires tool_execution_start and tool_execution_end events through session.subscribe(). Both Slack and Linear handlers already subscribe to these. A brain-file detection layer could be added to either the event subscriber or as a post-processing step on tool results.
  8. The "soul clause" is the only notification mechanism: memory.md says "If you change it, tell the user — it's your soul, and they should know." This is prompt-level guidance that fails when the agent doesn't consider an update important enough to mention — exactly what happened in the screenshot conversation.
  9. Slash command infrastructure exists: The /workflow slash command pattern is established in src/slack/client.ts and src/webhook-server.ts. Adding /vera memories for memory management would follow the same pattern.
  10. ChatGPT's approach (industry gold standard): Shows an inline "Memory updated" pill/badge before the response text. Users can hover to see what was saved. The notification is system-level, not LLM-generated — so it always appears regardless of whether the model "remembers" to mention it. This is the UX target.

Codebase Context

Component File Relevance
Brain file loading src/session/index.tsloadBrainFiles() Where brain content enters the prompt. brain: true opt-in per agent.
Brain file seeding src/paths.tsseedStateDir() Seeds new brain files from image but preserves agent modifications.
Tigris brain sync src/storage/tigris.tsstartTigrisSync() Already watches brain dir via fs.watch() — potential hook point.
Write tool src/tools/write.tscreateWriteTool() Generic, no brain-specific logic. Returns diff on overwrite.
Edit tool src/tools/edit.ts Generic, no brain-specific logic.
Slack response rendering src/slack/handler.tspostResponseWithReactions() Parses [react:*] directives. Could parse [memory:*] too.
Slack progress src/slack/progress.tsSlackProgressTracker Shows ephemeral tool status. Already surfaces brain edits transiently.
Linear activity src/linear/activity.ts Supports structured activity types for Linear UI.
Tool registry src/tools/registry.ts Central tool resolution. Where a dedicated memory tool would be registered.
Agent definitions src/agents/slack.md Only agent with brain: true.
Reaction directives src/slack/handler.tsextractReactionDirectives() Pattern for structured agent-output directives.

Sources

Recommendations

1. Harness-level detection via tool event subscriber (highest priority, cleanest path)

Add a detection layer in the Slack handler's session subscriber (and Linear runtime's subscriber) that checks tool_execution_end events for brain file paths. When detected:

  • Slack: Post a compact, visually distinct block after the main response: 🧠 Memory updated with the diff or summary as context. Use Slack's Block Kit context element for a subtle, ChatGPT-pill-like treatment.
  • Linear: Emit a thought or custom activity via activity.ts.

This is the least invasive approach — no tool changes needed, just a subscriber filter.

Implementation sketch:

// In Slack handler's session subscriber
case "tool_execution_end": {
  if (!ev.isError && isBrainFileEdit(ev.toolName, ev.args)) {
    pendingMemoryNotifications.push(extractMemorySummary(ev));
  }
  break;
}

2. Directive-based notification (medium priority, LLM-cooperative)

Extend the existing [react:emoji] directive pattern to support [memory:summary text]. The harness already parses directives from agent output — adding a memory directive type is straightforward. The agent would emit [memory:Learned that Stepan works 7 days/week] in its response, and the harness would render it as a distinct visual element.

Pros: Agent controls the summary text (more human-readable than a raw diff). Cons: Still depends on LLM compliance — same failure mode as the soul clause.

Best as a belt-and-suspenders addition to approach #1.

3. Dedicated update_memory tool (medium priority, structural improvement)

Register a tool in ToolRegistry that wraps brain file edits with structured metadata:

update_memory({ file: "memory.md", key: "schedule", value: "Works 7 days/week", summary: "Learned work schedule" })

The tool handler writes the file and returns structured result data that the harness can render. The tool's existence in the agent's tool list also serves as a stronger prompt signal than a soul clause.

Most robust approach but requires the most changes: new tool definition, agent prompt updates, and the agent must learn to prefer it over raw edit/write for brain files.

4. Leverage the existing Tigris fs.watch() (creative alternative)

startTigrisSync() already watches brain files. Extend its watcher callback to emit an event (e.g., via EventEmitter or callback) when a brain file changes. Slack/Linear handlers subscribe and post notifications. Fully LLM-independent — works even for manual brain file edits.

Downside: fs.watch doesn't know what changed, only that a file changed. You'd need to diff before/after content yourself.

5. Memory management surface (lower priority, future)

A /vera memories slash command or simple brain file dump. The slash command infrastructure is already in place (src/slack/client.tsonSlashCommand, src/webhook-server.ts routes). Adding /vera memories would be straightforward. Not urgent for MVP but important for long-term trust.

Open Questions

  1. Which agents should support memory notifications? Currently only slack has brain: true. The linear and main agents don't use brain files. If brain is enabled for more agents in the future, the notification system should be agent-agnostic.
  2. Granularity: Should tools.md and REACTIONS.md edits trigger the same notification as memory.md edits? They serve different purposes — memory.md is personal facts, tools.md is learned tool usage, REACTIONS.md is emoji behavior. The notification treatment might differ (e.g., 🧠 Memory updated vs. 📝 Learned a tool pattern).
  3. Self-note regression: The runtime tools.md at /workspace/.vera/brain/tools.md doesn't contain the "update memory immediately when you learn something" self-note. Was this intentional or did it get lost in a redeploy? This affects how proactively the agent updates memory.
  4. Block Kit vs. plain text: Should memory notifications use Slack Block Kit for rich formatting (expandable details, clickable "view memory" buttons) or stay as simple mrkdwn? Block Kit is more polished but adds rendering complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant