Skip to content

feat: agent memory & work log (Phase D)#25

Merged
michaelzwang13 merged 1 commit into
mainfrom
feat/agent-memory-and-work-log
May 24, 2026
Merged

feat: agent memory & work log (Phase D)#25
michaelzwang13 merged 1 commit into
mainfrom
feat/agent-memory-and-work-log

Conversation

@michaelzwang13
Copy link
Copy Markdown
Owner

Phase D — Agent memory & work log

Closes the memory loop and persists the audit trail. Phase B left a deny-only log line in policy.require_action; Phase D promotes that stub to a persisted agent_action_log row covering both allow and deny. Adds a per-agent key/value memory store the agent writes via a new update-memory skill and reads back as injected role_context on every dispatch — knowledge now survives container restarts. Pulls Phase C's reviewed_prs dedup index forward so the autonomous PR-review loop can build on a stable base.

Schema (migration 004 + schema.sql)

  • agent_memory (agent_id, key, value, updated_at)unique(agent_id, key)
  • agent_action_log (agent_id, action, outcome, metadata jsonb, created_at) with a CHECK constraint pinning outcome to allowed | denied
  • reviewed_prs (agent_id, owner, repo, pr_number, reviewed_at) with unique(agent_id, owner, repo, pr_number)
  • RLS closed-by-default policies mirrored across both files

Models

  • AgentMemoryModel.get / upsert / list_by_agent / delete
  • ActionLogModel.record / list_by_agent
  • ReviewedPRModel.exists / record / list_by_agent

Policy

  • require_action persists allow + deny rows. Audit writes are best-effort — a DB hiccup logs locally and lets the request proceed, trading audit completeness for availability; the policy decision itself never depends on the DB.

Dispatcher

  • dispatch_task now loads the calling agent's memory and injects it into role_context (best-effort; a memory-load failure does not block dispatch).

Gateway

  • GET / POST /gateway/memory — agent-authed, policy-gated via agent.memory.{read,write}. Per-agent only — memory is scoped by agent_id.
  • POST /github/review now inserts a reviewed_prs row on success, guarded by exists() so re-reviews don't double-write.

Skill + template

  • New update-memory skill — agent POSTs key/value pairs via the agent bearer token. Server-side persistence is what lets preferences survive container restart (SOUL.md cannot).
  • code-review-engineer.yaml: skills += update-memory; allowed_actions += agent.memory.read, agent.memory.write.

Tests

125/125 pass. 11 new:

  • policy — audit allow + deny + audit-failure swallowed
  • gateway — memory write/read/auth/policy/empty-key; reviewed_prs records on success + skips on dup
  • dispatcher — memory injection + best-effort failure

Live smoke (12/12 green against the running backend)

Write/read round-trip; upsert idempotent on key; 401 on bad/missing token; 403 when a secretary role hits /memory; agent_action_log carries the allow + deny rows with role metadata.

Operator note

Migration 004 needs to be re-run in the Supabase SQL Editor to pick up the three new tables (idempotent — create table if not exists makes re-runs safe). Agent image was rebuilt locally to include the new skill — docker build -t openclaw/agent:latest backend/agent-runtime/.

Out of scope, follow-up

Memory compaction (LLM reflection / LRU / clustering) is tracked in #23 — the forward-compatible defaults this PR sets (updated_at per row, all-keys injection at dispatch) keep that path open without schema changes.

Closes #8.

🤖 Generated with Claude Code

Closes the memory loop and persists the audit trail. Phase B left a
deny-only log line in policy.require_action; Phase D promotes that stub
to a persisted agent_action_log row covering both allow and deny. Adds
a per-agent key/value memory store the agent writes via a new
update-memory skill and reads back as injected role_context on every
dispatch — knowledge now survives container restarts. Pulls Phase C's
reviewed_prs dedup index forward so the autonomous PR-review loop can
build on a stable base.

Schema (migration 004 + schema.sql):
- agent_memory (agent_id, key, value, updated_at) — unique(agent_id, key)
- agent_action_log (agent_id, action, outcome, metadata jsonb, created_at)
  with a CHECK constraint pinning outcome to allowed | denied
- reviewed_prs (agent_id, owner, repo, pr_number, reviewed_at)
  with unique(agent_id, owner, repo, pr_number)
- RLS closed-by-default policies, mirrored across both files

Models:
- AgentMemoryModel.get / upsert / list_by_agent / delete
- ActionLogModel.record / list_by_agent
- ReviewedPRModel.exists / record / list_by_agent

Policy:
- require_action persists allow + deny rows. Audit writes are
  best-effort — a DB hiccup logs locally and lets the request proceed,
  trading audit completeness for availability; the policy decision
  itself never depends on the DB.

Dispatcher:
- dispatch_task now loads the calling agent's memory and injects it
  into role_context (best-effort; a memory-load failure does not
  block dispatch).

Gateway:
- GET / POST /gateway/memory — agent-authed, policy-gated via
  agent.memory.{read,write}. The agent's own row only — memory is
  scoped per agent_id.
- POST /github/review now inserts a reviewed_prs row on success,
  guarded by exists() so re-reviews don't double-write.

Skill + template:
- New update-memory skill — agent POSTs key/value pairs via the agent
  bearer token. Server-side persistence is what lets preferences
  survive container restart (SOUL.md cannot).
- code-review-engineer.yaml: skills += update-memory;
  allowed_actions += agent.memory.read, agent.memory.write.

Tests: 124/124 pass. 11 new — policy audit allow + deny + audit-failure
swallowed; memory write/read/auth/policy/empty-key; reviewed_prs
records on success + skips on dup; dispatcher injects memory + best-
effort failure.

Live smoke test against the running backend (12/12 green): write/read
round-trip, upsert idempotent on key, 401 on bad/missing token, 403
when a secretary role hits /memory, agent_action_log carries the
allow + deny rows with role metadata.

Closes #8.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@michaelzwang13
Copy link
Copy Markdown
Owner Author

@claude review


create unique index if not exists idx_agents_agent_token on agents(agent_token);

-- ── Phase D — agent memory & work log ────────────────────────────────────────
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc updates — CLAUDE.md compliance

CLAUDE.md L87 requires:

When behavior or setup changes, update the relevant md (README.md, LOCAL_SETUP.md, ROADMAP.md, this file) in the same change.

This PR introduces three categories of changes that cross that threshold but no doc files are updated:

  1. Setup changeLOCAL_SETUP.md should note that migration 004 must be re-run in Supabase to create agent_memory, agent_action_log, and reviewed_prs. The operator note lives only in the PR description right now and won't be discoverable after merge.
  2. Architecture changeCLAUDE.md's architecture section lists the backend routers/services. The two new GET /gateway/memory and POST /gateway/memory endpoints and the three new model files (AgentMemoryModel, ActionLogModel, ReviewedPRModel) are worth a line there.
  3. Behavioral change — agents now carry persistent memory across container restarts via the update-memory skill; this is non-trivial new platform behavior worth a mention in README.md.

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.

Phase D — Agent memory & work log

1 participant