Add typed reactions (approve/reject/in_progress/done/published) with
toggle semantics. Workflow state derived from highest-priority reaction.
New reactions package with model, SQLite store, and service layer.
REST API: POST/GET/DELETE /api/messages/{id}/reactions for toggle/query,
PUT /api/channels/{name}/settings for workflow config, GET by-state
endpoint for listing messages by workflow state.
MCP: react/unreact/get_reactions/list_by_state actions via bridge.
Web UI: WorkflowBadge (colored state pills) and ReactionPills (toggle
pills with agent names) components integrated into channel view.
Channel settings: auto_approve, stalemate_remind_after,
stalemate_escalate_after columns. CLI: channels update command.
Migration 013_reactions.sql adds message_reactions table and channel
workflow columns. 29+ new test cases across model and store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.3 KiB
5.3 KiB
Autonomous Implementation Summary: Message Reactions & Workflow States
Branch: 010-reactions-workflows
Date: 2026-03-18
Status: Complete (StalemateWorker extension deferred)
What Was Built
Message Reactions
- Toggle semantics: Add a reaction → added. Add same reaction again → removed. One per type per agent per message.
- 5 reaction types: approve, reject, in_progress, done, published
- Metadata support: JSON metadata on reactions (e.g.,
{"url": "https://..."}for published) - 100-reaction limit per message (safety)
Workflow State Derivation
- State computed from reactions: published > done > rejected > in_progress > approved > proposed
- No denormalization — state derived on read from reaction list
- Channel messages with no reactions → "proposed" state
- Terminal states (rejected, done, published) don't trigger stalemate checks
Channel Workflow Settings
auto_approve— skip proposed state for new messagesstalemate_remind_after— duration before reminder DM (default 24h)stalemate_escalate_after— duration before escalation to #approvals (default 72h)
REST API
POST /api/messages/{id}/reactions— toggle reaction (add or remove)GET /api/messages/{id}/reactions— get reactions + workflow stateDELETE /api/messages/{id}/reactions/{reaction}— remove reactionPUT /api/channels/{name}/settings— update workflow settingsGET /api/channels/{name}/messages/by-state?state=X— list messages by state
MCP Tools (via execute bridge)
react— add/toggle reaction on a messageunreact— remove a reactionget_reactions— query reactions and workflow statelist_by_state— list messages by workflow state in a channel
Web UI
- WorkflowBadge component: colored pills (yellow/green/blue/red/gray/cyan) per state
- ReactionPills component: grouped reaction pills with count, agent names on hover, click-to-toggle
- Published reactions with URL show clickable link icon
- Integrated into channel message view
Admin CLI
synapbus channels update --name X --auto-approve=true --stalemate-remind-after=12h --stalemate-escalate-after=48h
Files Created/Modified
New Files
| File | Description |
|---|---|
internal/storage/schema/013_reactions.sql |
Migration: message_reactions table + channel columns |
internal/reactions/model.go |
Reaction types, state derivation, constants |
internal/reactions/store.go |
SQLite CRUD for reactions |
internal/reactions/service.go |
Business logic: toggle, remove, get, list by state |
internal/reactions/model_test.go |
23 test cases for model functions |
internal/reactions/store_test.go |
6 test functions for store operations |
internal/api/reactions_handler.go |
REST API handlers for reactions |
web/src/lib/components/WorkflowBadge.svelte |
Colored state badge component |
web/src/lib/components/ReactionPills.svelte |
Reaction toggle pills component |
Modified Files
| File | Changes |
|---|---|
internal/messaging/types.go |
Added WorkflowState, Reactions, ReactionInfo to Message |
internal/messaging/service.go |
Added ReactionEnricher interface, enrichment in EnrichMessages |
internal/channels/types.go |
Added AutoApprove, StalemateRemindAfter, StalemateEscalateAfter, ChannelSettings |
internal/channels/store.go |
Updated SELECT queries for new columns, added UpdateChannelSettings |
internal/channels/service.go |
Added UpdateChannelSettings method |
internal/api/router.go |
Registered reaction and channel settings routes |
internal/api/channels_handler.go |
Added UpdateSettings, ListByState handlers |
internal/mcp/bridge.go |
Added react/unreact/get_reactions/list_by_state bridge methods |
internal/mcp/tools_hybrid.go |
Added reactionService to registrar |
internal/mcp/server.go |
Added reactionService parameter |
internal/actions/registry.go |
Registered 4 new reaction actions |
cmd/synapbus/main.go |
Wired reaction service, adapter, passed to router+MCP |
cmd/synapbus/admin.go |
Added channels update CLI command |
internal/admin/socket.go |
Added channels.update_settings handler |
web/src/lib/api/client.ts |
Added reactions.toggle/get methods |
web/src/routes/channels/[name]/+page.svelte |
Integrated WorkflowBadge + ReactionPills |
Test Results
- 25 Go test packages: all pass, 0 failures
- New tests: 29+ test cases (model: 23, store: 6)
- Integration tests: 9 E2E tests pass
- Web build: Svelte SPA builds successfully
- Binary build: Compiles cleanly
Deferred
- StalemateWorker extension (T023-T025): The data model, channel settings, and query infrastructure are in place. The worker just needs a scan loop added to detect stale messages and send DMs/escalations. This is a straightforward follow-up task.
Architecture Decisions
- Separate reactions package: Clean domain separation from messaging
- Toggle semantics: INSERT if absent, DELETE if present — simple, atomic, idempotent
- Derived workflow state: No denormalization; state computed from reactions on read
- Bridge actions (not hybrid tools): Consistent with attachments pattern — 4 hybrid tools are stable surface area
- ReactionEnricher adapter: Avoids circular dependency between reactions and messaging packages