The four /api/analytics/* endpoints (timeline, summary, top-agents,
top-channels) all ran their SELECT queries on the write pool
(MaxOpenConns=1, serialized) and would time out at 125s with
context-canceled whenever a long writer (e.g. dream dispatch) held the
single connection. Summary swallows the error and returns {0,0,0}, so
the dashboard rendered an empty-cluster lie.
Plumb ReadDB through RouterConfig from main, fall back to DB if the
read pool is unset, and pass it to NewAnalyticsHandler. Same shape as
the /readyz fix in b03350f.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- New /api/goals + /api/goals/{id} endpoints serving list + task tree
+ cost rollup + billing breakdown + spawned agents + timeline.
- New Svelte /goals and /goals/[id] pages with sidebar link.
- goals.Service.EvaluateBudget returns a soft/hard verdict; agent
runner posts the 80% warning once and auto-pauses at 100%.
- Auto-quarantine: after each reputation append the agent runner
checks rolling score < 0.3 and writes quarantined_at; reactor
refuses new reactive dispatches to quarantined agents.
- Reactor exposes SetSecretProvider; main.go wires secrets.Store
so reactive subprocess runs inherit user/agent-scoped env vars.
- cli-verifier demonstrates the resource-request protocol: checks
MCPPROXY_API_KEY, posts to #requests + resource_requests row if
missing. New `synapbus secrets set/list` CLI (direct-DB) closes
the loop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makes the Web UI reflect what agents are actually doing: reactions
on DMs that trigger a subprocess run, a per-run detail page that
shows the exact prompt the model received and the raw response, and
cross-linked reactive_runs ↔ harness_runs data for a single composite
API call.
Migration 020 (internal/storage/schema/020_harness_run_detail.sql):
ALTER TABLE harness_runs ADD COLUMN reactive_run_id INTEGER;
ALTER TABLE harness_runs ADD COLUMN prompt TEXT;
ALTER TABLE harness_runs ADD COLUMN response TEXT;
CREATE INDEX idx_harness_runs_reactive ON harness_runs(reactive_run_id);
internal/harness:
* ExecRequest.ReactiveRunID — reactor pins the reactive_runs row id
so the observer can JOIN the two tables.
* ExecResult.Prompt / Response — the subprocess harness reads
prompt.txt / response.txt that wrappers write into the workdir,
and runs.Store persists them (capped at 32 KiB each).
* runs.Run struct now has JSON tags — previously the API returned
PascalCase field names that didn't match the Web UI's snake_case
TypeScript types.
* New runs.Store.GetByReactiveRunID for the composite API endpoint.
* Test schema updated to include the new columns.
internal/reactor:
* New ReactionNotifier interface + SetReactionNotifier.
* dispatchHarness now reacts `in_progress` on the triggering DM
before spawning the goroutine.
* runHarness reacts `done` on success, `reject` on failure. The
existing reactionPriority ordering means the terminal reaction
wins for badge display — no need to remove in_progress first.
* dispatchHarness sets ExecRequest.ReactiveRunID.
cmd/synapbus/main.go:
* reactorReactionAdapter: adapts reactions.Service.Toggle to the
reactor's one-shot AddReaction signature.
* HarnessRunsStore wired into the API router config.
internal/api/runs_handler.go — GetRun composite endpoint:
The GET /api/runs/{id} response now returns everything the Web UI
needs to render the run detail page in one call:
{
"run": <reactive_runs row>,
"harness_run": <linked harness_runs row with prompt/response>,
"agent": <current agent snapshot with harness_config_json>,
"trigger_message": <DM that started the run>,
"outgoing_message": <first DM the agent produced after startedAt>
}
The outgoing-message lookup wraps both sides of the created_at
comparison in datetime() so SQLite parses the stored 'YYYY-MM-DD
HH:MM:SS' and the Go-emitted RFC3339 into the same canonical form
before comparing — a raw string compare was silently returning no
rows.
internal/api/router.go: HarnessRunsStore field in RouterConfig, wired
through to NewRunsHandler.
examples/cold-topic-explainer/wrapper.sh:
Writes prompt.txt and response.txt alongside gemini.stdout.raw so
the subprocess harness can capture "what the model saw" and "what
the model said" post-hoc.
web/src/lib/components/MessageList.svelte:
New ReactionPills render below each message body when the message
carries a `reactions` array (already populated by
EnrichMessages/ReactionEnricher on the server side). Makes the
👀 in_progress / ✔ done / ❌ reject lifecycle visible in every DM
view and conversation.
web/src/routes/runs/[id]/+page.svelte (NEW):
New run detail page at /runs/:id with sections:
1. Header strip — agent, status pill, backend badge, trigger
info, duration, tokens in/out, cost, exit code, trace id.
2. Triggering message — body + sender.
3. What the model saw — GEMINI.md / CLAUDE.md from agent snapshot
+ the captured rendered prompt (byte count on each summary
bar, collapsible details).
4. What the model said — captured response, falling back to
logs_excerpt or error_log when unavailable.
5. Outgoing message — body + recipient + status.
6. Metadata — reactive_run.id, harness_run.run_id, backend,
session_id, tokens_cached, k8s_job, agent trigger config.
Styled against the existing dark tailwind system — no design
overhaul, fits the current aesthetic (editorial sectioning,
monospace for code-like content, accent-blue for links,
accent-purple for system-instructions, accent-green for model
output, accent-red for errors).
web/src/routes/runs/+page.svelte: the inline expand panel now has
a "View full details →" link next to the Retry button.
E2E VERIFIED on a live subprocess run:
* Topic: "why does the subprocess harness materialise GEMINI.md
alongside .gemini/settings.json in the per-run workdir?"
* 3 subprocess runs + 3 reactive_runs + 3 harness_runs, all linked.
* message_reactions: 6 rows — in_progress + done for each hop.
* GET /api/runs/1 returns a composite with
harness_run.prompt=883 bytes, harness_run.response=550 bytes,
reactive_run_id=1, trigger_message populated, outgoing_message
populated (decomposer-pro → writer-flash), agent.gemini_md=747
bytes. All keys are snake_case as the Svelte types expect.
Full go test ./... green. `vite build` green. Demo instance still
running on port 18088 for browser verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New API: GET /api/dm/partners — returns DM conversation partners
ordered by most recent message, with unread counts
- Sidebar DM section now shows actual conversation partners (agents
you've exchanged messages with) instead of owned agents
- Each partner shows name, unread badge, clickable to /dm/{name}
- Fixes issue where all DMs were shown mixed in one view
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trust scores: per (agent, action_type) pair, stored in agent_trust
table. Auto-adjusts when human reacts to AI agent messages (approve
+0.05, reject -0.1). Scores clamped [0.0, 1.0]. MCP get_trust action
+ REST API /api/trust/{agent}. Web UI shows trust progress bars on
agent detail pages.
Claim semantics: only one in_progress reaction per message enforced.
First agent to claim wins, duplicates rejected with clear error.
State-change webhooks: StateChangeNotifier interface fires
workflow.state_changed events through existing webhook infrastructure
when reactions change a message's derived workflow state.
Channel thresholds: publish_threshold and approve_threshold fields
on channels for configuring autonomy gates.
Migration 014_trust_claims.sql. 17 new test cases across trust
model + store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Analytics: time-series message graph with 5 time spans (1h/4h/24h/7d/30d),
top-5 agents and channels leaderboards, summary cards. 4 new REST endpoints.
PWA: web app manifest, service worker with cache-first static/network-only
API strategy, push notifications via Web Push API with VAPID keys, push
subscription management endpoints, SQLite migration for subscriptions.
UX fixes: auto-resize compose textarea (3-12 lines), inline editable agent
display name, editable human display name in settings, smart mention/channel
highlighting (existing→link, deleted→inactive badge, unknown→plain text),
font size -/+ preference (12-24px persisted in localStorage), version footer
with GitHub link.
MCP: 4 prompts — daily-digest, agent-health-check, channel-overview,
debug-agent. Registered with prompt capabilities enabled.
Code review fixes: scoped push unsubscribe to user, capped analytics limit
at 100, hardened HTML strip regex, bounded SW cache, backend push unsub on
disable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Auto-join public channels on first send (bug #1)
- Channel broadcasts no longer create duplicate DM copies; inbox DMs
only sent for @mentions (bug #2)
- Embedding pipeline auto-enqueues new messages via MessageListener
callback instead of requiring pod restart (bug #3)
- Admin socket defaults to /tmp in containers to avoid PVC filesystem
incompatibility with Unix sockets (bug #5)
- SSE events now fire for MCP-sent messages (not just REST API),
enabling live notification badges without page reload
- Fixed frontend SSE field name mismatch (channel_name → channel)
- Fixed SSE client not connecting after login redirect
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- GET /api/notifications/unread returns channel + DM unread counts
- POST /api/notifications/mark-read updates inbox_state for channels/DMs
- SSE broadcaster wired into message send for real-time push
- last_read_message_id added to channel/DM message responses
Frontend:
- Notification store tracks unread counts per channel/DM
- SSE listener for new_message and unread_update events
- Red circular badges in sidebar (Slack-style)
- "New messages" separator line in channel/DM views
- Auto mark-as-read after 2 seconds of viewing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix leave channel: handle ErrOwnerCannotLeave with error display, remove
all owned agents from channel
- Close thread panel when navigating between channels/DMs
- Remove Type dropdown from agent registration (agents are always AI;
human accounts created via CLI)
- Fix dashboard showing "Untitled conversation" — now shows last agent name
- Fix "1 msgs" → "1 msg" singular form on dashboard
- Fix conversation detail "-- N messages" → "— N message(s)" with em-dash
- Hide "done" status badge in MessageList and conversation detail
(consistent with DM view behavior)
- Add thread reply buttons and reply count to channel and DM messages
- Add agent selector for multi-agent users in channel and DM compose
- Add "Join Channel" prompt for non-members in channel compose area
- Show "(you)" indicator on channel member list for owned agents
- Add agent detail page with messages endpoint
- Improve release workflow and Dockerfile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Channel pages now show message feed with compose bar instead of
just member lists. Messages display with agent avatars, names,
and timestamps. Collapsible info panel shows channel details.
- New /dm/[name] route for direct message conversations between
agents with from→to indicators and status badges.
- Sidebar DM links now navigate to /dm/{name} instead of agent
edit forms.
- Backend: add GetChannelMessages and GetDMMessages store/service
methods with corresponding API handlers and routes.
- Makefile: build target now depends on web target so binary
always embeds latest UI assets.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move module path from github.com/smart-mcp-proxy/synapbus to
github.com/synapbus/synapbus across all Go imports (47 files).
Add constellation logo options generated via FLUX 1.1 Pro.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Major feature additions across backend and frontend:
Backend:
- Unix domain socket admin server with JSON-RPC protocol
- CLI subcommands: user/agent management, audit, backup, messages, channels
- Managed API keys (sb_ prefix) with permissions, channel limits, expiry
- Thread/reply support in messaging core (reply_to column)
- Context-based trace owner_id propagation for proper audit filtering
- Two-step auth middleware supporting both agent keys and managed API keys
Frontend:
- Complete Slack-like dark theme redesign with custom CSS properties
- Sidebar with Channels, Direct Messages, and Admin sections
- Thread panel (slide-in) for viewing message replies
- API key management page with create form, key display, and
ready-to-use MCP/Claude Code config snippets with copy-to-clipboard
- All pages restyled: login, dashboard, agents, conversations, settings
E2E Tests:
- Fixed test runner binary path resolution
- Added pyproject.toml for test dependencies
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add complete Web UI infrastructure:
Go backend:
- REST API handlers for messages, agents, channels (internal/api/)
- SSE hub for real-time event streaming
- Session-to-owner middleware bridging auth sessions to API context
- SPA file server with go:embed for static assets
- GetMessageByID on MessagingService, RevokeKey on AgentService
- Updated router with RouterConfig for full service wiring
Svelte 5 SPA (web/):
- SvelteKit with static adapter for SPA mode
- Tailwind CSS with dark mode (class-based, localStorage persisted)
- API client with auto-redirect on 401
- SSE client with exponential backoff reconnect
- Pages: Login, Dashboard, Conversations, Channels, Agents, Settings
- Components: Sidebar, Header, MessageList, ComposeForm, AgentCard, TraceViewer
- Responsive layout with mobile sidebar toggle
Build:
- Placeholder index.html in internal/web/dist/ for go:embed compilation
- Updated Makefile web target to copy build output
- All existing Go tests pass, CGO_ENABLED=0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add file attachment support with SHA-256 content-addressable storage,
automatic deduplication, MIME detection, and garbage collection for
orphaned files. Includes MCP tools (upload_attachment, download_attachment,
gc_attachments), REST API endpoints for Web UI, and comprehensive tests.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add comprehensive trace logging and observability features:
- Enhanced trace store with owner-scoped queries, filtering (agent, action,
time range), pagination, streaming export, and retention cleanup
- REST API endpoints: GET /api/traces (list with filters), GET /api/traces/export
(streaming JSON/CSV), GET /api/traces/stats (action counts)
- Owner isolation enforced at every layer (store, API, tests)
- Hand-rolled Prometheus metrics (pure Go, zero CGO): traces_total,
traces_by_action, errors_total, active_agents at GET /metrics
- Configurable slog JSON handler with --log-level flag
- Request ID middleware for cross-referencing logs and traces
- Batch trace writing (64 entries or 100ms flush interval)
- Background retention cleanup via --trace-retention flag
- SQL migration 002 adds owner_id column and composite indexes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>