Skip to content

feat(slack-app phase 3b): Anthropic agent loop + read-only memory tools#28

Merged
TS00 merged 1 commit intodevfrom
feat/slack-agent-phase3b
Apr 28, 2026
Merged

feat(slack-app phase 3b): Anthropic agent loop + read-only memory tools#28
TS00 merged 1 commit intodevfrom
feat/slack-agent-phase3b

Conversation

@TS00
Copy link
Copy Markdown
Collaborator

@TS00 TS00 commented Apr 28, 2026

Summary

Phase 3b — replaces the Phase 3a canned reply with the actual agent. The bot now answers Slack messages by reading and searching the requesting user's Reflect memories.

Full design: `docs/eng-plan-slack-app-v1.md`. Parent memory `d959bc61`.

New modules

  • `src/slack-conversation-service.ts` — per-thread state in `slack_conversation_state`. 24h TTL, MAX 20 user/assistant turn pairs (older truncated). Stores text-only — tool-use turns aren't persisted across messages (they bloat fast and don't help on the next turn). Lazy `pruneExpiredConversations` on every save (cheap, indexed).
  • `src/slack-agent-tools.ts` — 8 read-only memory tools: `get_memory_briefing`, `search_memories`, `read_memories`, `get_memories_by_tag`, `get_memory_by_id`, `read_team_memories`, `read_thread`, `get_graph_around`. Each is a thin shim over the existing memory-service / memory-graph functions, scoped to the resolved Reflect user, vendor='slack', limits clamped to 25.
  • `src/slack-agent.ts` — `runSlackAgentTurn`. Builds the system prompt (mode + speaker), runs the Anthropic SDK loop, executes tool calls in parallel per step, caps at 6 steps. Default model `RM_SLACK_AGENT_MODEL` or `claude-sonnet-4-7`. Anthropic client is injectable for tests.

Wiring

`slack-events-handler.ts`: handler now loads the LLM key for the workspace's scope, loads conversation history for the thread, runs the agent turn, persists the new history, posts the reply. Falls back to a polite "set an LLM key in dashboard" message when no key is configured. Audit metadata gains `tool_calls` / `agent_steps` / `stop_reason`.

Tests (+20, 367/367 total)

slack-conversation: roundtrip, update-not-duplicate, MAX_TURNS truncation, prune of expired rows, fail-closed on corrupted JSON, defensive filtering of malformed message shapes.

slack-agent: every tool's dispatch path against a real DB + end_turn + tool_use loop with a stubbed Anthropic client + empty-text fallback + system prompt encoding (DM vs channel, speaker).

Manual prereq for end-to-end validation

Before the agent can reply on dev, you need to set the Anthropic API key:

  1. Open https://dev.reflectmemory.com/dashboard/connections/slack
  2. In the LLM provider key section, paste your Anthropic key (sk-ant-...) and click Save.
  3. Without this, the bot will reply with a polite "set an LLM key in dashboard" message instead.

Test plan

  • CI green
  • Dev deploy succeeds
  • Set Anthropic key in dashboard
  • DM the bot something open-ended ("what have I been working on?") → expect a real response that references your memories by title
  • @mention in a channel with a specific question ("what did we decide about X?") → expect a tool call to search_memories or get_memory_briefing then a focused reply
  • Continue the same thread with a follow-up → expect the agent to remember context (24h TTL on conversation state)
  • DM without an LLM key set → expect the polite fallback message

Made with Cursor

Replaces the Phase 3a canned reply with the real agent. The bot now
actually answers questions by reading + searching the requesting user's
memories.

New modules:
- src/slack-conversation-service.ts: per-Slack-thread state in
  slack_conversation_state. 24h TTL. MAX 20 user/assistant turn pairs
  (older truncated). Stores text-only — tool-use turns aren't
  persisted across messages (they bloat fast and don't help the model).
  Lazy pruneExpiredConversations call on every save (cheap, indexed).
- src/slack-agent-tools.ts: 8 read-only memory tools
  (get_memory_briefing, search_memories, read_memories,
  get_memories_by_tag, get_memory_by_id, read_team_memories,
  read_thread, get_graph_around). Each is a thin shim over the
  existing memory-service / memory-graph functions, scoped to the
  resolved Reflect user, vendor='slack', limits clamped to 25.
- src/slack-agent.ts: runSlackAgentTurn. Builds the system prompt
  (mode + speaker), runs the Anthropic SDK loop, executes tool calls
  in parallel per step, caps at 6 steps. Default model
  RM_SLACK_AGENT_MODEL or claude-sonnet-4-7. Anthropic client is
  injectable for tests.

Wiring:
- slack-events-handler.ts: handleUserMessage now loads the LLM key
  for the workspace's scope, loads conversation history for the
  thread, runs the agent turn, persists the new history, posts the
  reply. Falls back to a polite "set an LLM key" message if no key
  is configured. Audit metadata gains tool_calls / agent_steps /
  stop_reason.

Tests (+20, total 347 -> 367):
- slack-conversation: roundtrip, update-not-duplicate, MAX_TURNS
  truncation, prune of expired rows, fail-closed on corrupted JSON,
  defensive filtering of bad shapes.
- slack-agent: every tool's dispatch path against a real DB +
  end_turn + tool_use loop with a stubbed Anthropic client +
  empty-text fallback + system prompt encoding (DM vs channel,
  speaker).

Refs: parent memory d959bc61.
Made-with: Cursor
@TS00 TS00 merged commit 939e9bf into dev Apr 28, 2026
4 checks passed
@TS00 TS00 deleted the feat/slack-agent-phase3b branch April 28, 2026 23:31
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