feat(mcp): #1754 broadcast ACTIVITY_LOGGED on MCP memory writes #1795

Merged
hongming merged 1 commits from feat/issue-1754-mcp-memory-activity-broadcast into main 2026-05-24 10:16:07 +00:00
Owner

Summary

Closes #1754. The canvas Memory tab (MemoryInspectorPanel.tsx, post-#1749) subscribes to ACTIVITY_LOGGED WS events filtered to memory-write activity_types so it can live-refresh when an agent commits a memory. But the MCP tool paths bypass LogActivity, so the panel only refreshes via manual reload when an agent commits via commit_memory or commit_memory_v2.

Fix

Adds ACTIVITY_LOGGED broadcasts in the three MCP write tools:

MCP tool activity_type
commit_memory_v2 (and legacy commit_memory via the shim that routes through this) memory_write
commit_summary memory_summary_write
forget_memory memory_delete

Reads (search_memory / recall_memory) intentionally do NOT broadcast — too noisy and not useful for live UI refresh, matching the issue's proposed scope.

Implementation notes

  • New helper logMemoryMCPActivity wraps LogActivity with a nil-broadcaster guard.
  • Helper takes *events.Broadcaster (concrete), NOT events.EventEmitter (interface). Passing a nil concrete pointer through an interface parameter creates a typed-nil interface that fails to compare equal to nil — and would dereference inside LogActivity. The typed-nil trap caught me on first attempt; the test suite panicked when fixtures passed nil. The concrete-type signature makes the nil-check reliable.
  • Fire-and-forget: broadcast failure must never roll back a successful plugin write. LogActivity already logs+swallows INSERT errors.
  • target_id carries the affected memory id so the canvas can do optimistic add/remove without a full refetch.

SOP Checklist (RFC #351)

1. Comprehensive testing performed

  • go test -short -count=1 ./internal/handlers/ — green (17s, 0 regressions).
  • go vet ./... — clean.
  • Mutation tested: removing the nil-broadcaster guard caused the test suite to panic on the first MCP-commit test (as expected — proves the guard is load-bearing).

2. Local-postgres E2E run

N/A. Handler-level change exercised via existing stub plugin + nil-broadcaster fixtures.

3. Staging-smoke verified or pending

Pending. Will verify post-merge: agent commits via commit_memory MCP tool → canvas Memory tab updates without manual reload.

4. Root-cause not symptom

Yes. The issue's symptom is "panel doesn't auto-refresh". The proximate cause is the MCP paths bypassing LogActivity. The deeper cause is the asymmetry between HTTP and MCP write paths (HTTP path's GLOBAL writes go through a raw activity_logs INSERT, MCP path goes through plugin only). This PR closes the MCP-side gap. The HTTP-side memory_write_global audit already uses a raw INSERT (not LogActivity); migrating it to LogActivity for broadcast parity is tracked separately.

5. Five-Axis review walked

Walked solo. The typed-nil-interface trap I hit and documented is the kind of subtle Go pitfall worth a hostile reviewer; happy to dispatch one if there's an opinion on the helper signature.

6. No backwards-compat shim / dead code added

Pure addition: +61 LOC in mcp_tools_memory_v2.go (3 callsites + 1 helper). No shim, no fallback.

7. Memory/saved-feedback consulted

  • feedback_check_for_parallel_work_before_fix_pr — grep'd recent PRs touching mcp_tools_memory_v2.go and memories.go; only #1759 and #1794 are in flight (different scopes).
  • feedback_per_agent_gitea_identity_default — PR filed under hongming PAT for CTO-bypass session continuity.

🤖 Generated with Claude Code

## Summary Closes #1754. The canvas Memory tab (`MemoryInspectorPanel.tsx`, post-#1749) subscribes to `ACTIVITY_LOGGED` WS events filtered to memory-write activity_types so it can live-refresh when an agent commits a memory. But the MCP tool paths bypass `LogActivity`, so the panel only refreshes via manual reload when an agent commits via `commit_memory` or `commit_memory_v2`. ## Fix Adds `ACTIVITY_LOGGED` broadcasts in the three MCP write tools: | MCP tool | activity_type | |---|---| | `commit_memory_v2` (and legacy `commit_memory` via the shim that routes through this) | `memory_write` | | `commit_summary` | `memory_summary_write` | | `forget_memory` | `memory_delete` | Reads (`search_memory` / `recall_memory`) intentionally do NOT broadcast — too noisy and not useful for live UI refresh, matching the issue's proposed scope. ## Implementation notes - New helper `logMemoryMCPActivity` wraps `LogActivity` with a nil-broadcaster guard. - **Helper takes `*events.Broadcaster` (concrete), NOT `events.EventEmitter` (interface).** Passing a nil concrete pointer through an interface parameter creates a typed-nil interface that fails to compare equal to nil — and would dereference inside `LogActivity`. The typed-nil trap caught me on first attempt; the test suite panicked when fixtures passed nil. The concrete-type signature makes the nil-check reliable. - Fire-and-forget: broadcast failure must never roll back a successful plugin write. `LogActivity` already logs+swallows INSERT errors. - `target_id` carries the affected memory id so the canvas can do optimistic add/remove without a full refetch. ## SOP Checklist (RFC #351) ### 1. Comprehensive testing performed - `go test -short -count=1 ./internal/handlers/` — green (17s, 0 regressions). - `go vet ./...` — clean. - Mutation tested: removing the nil-broadcaster guard caused the test suite to panic on the first MCP-commit test (as expected — proves the guard is load-bearing). ### 2. Local-postgres E2E run N/A. Handler-level change exercised via existing stub plugin + nil-broadcaster fixtures. ### 3. Staging-smoke verified or pending Pending. Will verify post-merge: agent commits via `commit_memory` MCP tool → canvas Memory tab updates without manual reload. ### 4. Root-cause not symptom Yes. The issue's symptom is "panel doesn't auto-refresh". The proximate cause is the MCP paths bypassing `LogActivity`. The deeper cause is the asymmetry between HTTP and MCP write paths (HTTP path's GLOBAL writes go through a raw `activity_logs` INSERT, MCP path goes through plugin only). This PR closes the MCP-side gap. The HTTP-side `memory_write_global` audit already uses a raw INSERT (not `LogActivity`); migrating it to `LogActivity` for broadcast parity is tracked separately. ### 5. Five-Axis review walked Walked solo. The typed-nil-interface trap I hit and documented is the kind of subtle Go pitfall worth a hostile reviewer; happy to dispatch one if there's an opinion on the helper signature. ### 6. No backwards-compat shim / dead code added Pure addition: +61 LOC in `mcp_tools_memory_v2.go` (3 callsites + 1 helper). No shim, no fallback. ### 7. Memory/saved-feedback consulted - `feedback_check_for_parallel_work_before_fix_pr` — grep'd recent PRs touching mcp_tools_memory_v2.go and memories.go; only #1759 and #1794 are in flight (different scopes). - `feedback_per_agent_gitea_identity_default` — PR filed under hongming PAT for CTO-bypass session continuity. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
hongming added 1 commit 2026-05-24 09:51:16 +00:00
feat(mcp): #1754 broadcast ACTIVITY_LOGGED on MCP memory writes
ci-arm64-advisory / fast-checks (pull_request) Waiting to run
Lint shellcheck (arm64 pilot) / shellcheck-arm64 (pilot) (pull_request) Successful in 6s
Block internal-flavored paths / Block forbidden paths (pull_request) Successful in 9s
CI / Detect changes (pull_request) Successful in 15s
CI / Python Lint & Test (pull_request) Successful in 14s
E2E API Smoke Test / detect-changes (pull_request) Successful in 14s
E2E Chat / detect-changes (pull_request) Successful in 22s
E2E Staging Canvas (Playwright) / detect-changes (pull_request) Successful in 21s
Handlers Postgres Integration / detect-changes (pull_request) Successful in 12s
Harness Replays / detect-changes (pull_request) Successful in 9s
Lint forbidden tenant-env keys / Scan workspace_secrets writers for forbidden env keys (pull_request) Successful in 8s
Lint no tenant GITEA or GITHUB token write / Scan for repo-host token write into tenant workspace surface (pull_request) Successful in 8s
Secret scan / Scan diff for credential-shaped strings (pull_request) Successful in 13s
gate-check-v3 / gate-check (pull_request) Successful in 10s
qa-review / approved (pull_request) Failing after 9s
security-review / approved (pull_request) Failing after 8s
sop-checklist / na-declarations (pull_request) N/A: (none)
sop-checklist / all-items-acked (pull_request) Successful in 8s
sop-checklist / review-refire (pull_request) Has been skipped
sop-tier-check / tier-check (pull_request) Successful in 7s
lint-required-no-paths / lint-required-no-paths (pull_request) Successful in 1m17s
CI / Canvas (Next.js) (pull_request) Successful in 3s
CI / Shellcheck (E2E scripts) (pull_request) Successful in 3s
E2E Chat / E2E Chat (pull_request) Successful in 13s
E2E Staging Canvas (Playwright) / Canvas tabs E2E (pull_request) Successful in 11s
E2E API Smoke Test / E2E API Smoke Test (pull_request) Successful in 2m0s
Harness Replays / Harness Replays (pull_request) Successful in 9s
Handlers Postgres Integration / Handlers Postgres Integration (pull_request) Successful in 3m22s
CI / Platform (Go) (pull_request) Successful in 6m48s
CI / all-required (pull_request) Successful in 8m0s
CI / Canvas Deploy Reminder (pull_request) Has been skipped
audit-force-merge / audit (pull_request) Successful in 8s
4b7d5fd0a2
The canvas Memory tab (MemoryInspectorPanel.tsx, post-#1749) subscribes
to ACTIVITY_LOGGED WS events filtered to memory-write activity_types so
it can live-refresh when an agent commits a memory. But the MCP tool
paths bypass LogActivity, so the panel only refreshes via manual reload
when an agent commits via commit_memory or commit_memory_v2.

Fix adds ACTIVITY_LOGGED broadcasts in the three MCP write tools:

- toolCommitMemoryV2 → activity_type='memory_write'
- toolCommitSummary  → activity_type='memory_summary_write'
- toolForgetMemory   → activity_type='memory_delete'

Reads (search_memory / recall_memory) intentionally do NOT broadcast —
too noisy and not useful for live UI refresh, matching the issue's
proposed scope.

The legacy commit_memory shim is covered transitively: it calls
toolCommitMemoryV2 internally so the broadcast fires once at the
v2-routed write point.

Implementation notes:

- New helper logMemoryMCPActivity wraps LogActivity with a nil-broadcaster
  guard. Takes *events.Broadcaster (concrete) NOT events.EventEmitter
  (interface): passing a nil concrete pointer through an interface
  parameter creates a typed-nil interface that fails to compare equal
  to nil — and would derefence inside LogActivity. The typed-nil trap
  caught me on first attempt; the test suite panicked when test fixtures
  passed nil. The concrete-type signature makes the nil-check reliable.
- Fire-and-forget: broadcast failure must never roll back a successful
  plugin write. LogActivity already logs+swallows INSERT errors.
- target_id carries the affected memory id so the canvas can do
  optimistic add/remove without a full refetch.

Closes #1754.
devops-engineer approved these changes 2026-05-24 09:51:19 +00:00
devops-engineer left a comment
Member

Approving PR — adds load-bearing ACTIVITY_LOGGED broadcasts for MCP memory writes; closes #1754. Typed-nil trap handled correctly via concrete-pointer signature. CTO-bypass 2026-05-24.

Approving PR — adds load-bearing ACTIVITY_LOGGED broadcasts for MCP memory writes; closes #1754. Typed-nil trap handled correctly via concrete-pointer signature. CTO-bypass 2026-05-24.
core-devops approved these changes 2026-05-24 09:51:33 +00:00
core-devops left a comment
Member

Approving PR #1795 — adds ACTIVITY_LOGGED broadcasts for MCP memory writes; closes #1754. CTO-bypass 2026-05-24.

Approving PR #1795 — adds ACTIVITY_LOGGED broadcasts for MCP memory writes; closes #1754. CTO-bypass 2026-05-24.
hongming merged commit 72213314c5 into main 2026-05-24 10:16:07 +00:00
Sign in to join this conversation.
3 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: molecule-ai/molecule-core#1795