Gateway Module — `activity/`
1. Purpose
The activity module is the gateway's observability event log — a mission-scoped feed of agent tool/skill usage (cache_query, memory_search, agent_memory_query, peer_memory_query, skill_usage, tool_call, skill_gate). Beyond logging, it carries a load-bearing side effect: every log_activity call with a non-null agent_name ticks the stall-detector heartbeat on that agent's in-flight dispatch rows (D1175/AK-345), aligning the MCP-side liveness signal with the dashboard activity feed. It also emits activity timeline events onto the shared timelineBus for SSE consumers, and computes skill-usage aggregates.
2. File Inventory
| File | Lines | Responsibility |
|---|---|---|
repository.ts | 209 | activity_log prepared statements; heartbeat tick; timeline emission; skill-usage stats |
routes.ts | 119 | 3 endpoints; VALID_LOG_TYPES manual validation; event_type filters |
| Total | 328 |
No index.ts/types.ts — types are inline exports in repository.ts; router wired directly (createActivityRouter(activityRepo), mount index.ts:738).
3. Public API Surface
REST Endpoints (mount: index.ts:738 → /api/activity)
| Method | Path | Auth | Body | Response | Side Effects |
|---|---|---|---|---|---|
| POST | /api/activity/log | Bearer | {event_type, agent_name, mission_id?, detail} (manual validation, not Zod) | {ok:true} | INSERT activity_log; ticks agent_dispatch.last_heartbeat by agent; emits activity timeline event. 400 on invalid event_type/missing agent_name/missing detail |
| GET | /api/activity/?limit=&event_type= | Bearer | — | {data: ActivityEvent[]} | Recent across all missions; optional event_type filter (validated vs VALID_LOG_TYPES, 400 on invalid) |
| GET | /api/activity/:missionId?limit=&agent=&event_type= | Bearer | — | {data: ActivityEvent[]} | Per-mission; composable agent+event_type (in-memory compose per Fuda §10.2) |
MCP Tools
log_activity (legacy permissive-flat, no raw twin → /log). Documented in the mcp module: the REST endpoint uses manual destructure + VALID_LOG_TYPES set, not Zod, so it is tech-debt-flagged (harden Zod before converting to raw).
4. Internal API
createActivityRepository(db) → ActivityRepository { log, findByMission, findByMissionAndAgent, findRecent, findRecentByEventType, findByMissionAndEventType, getSkillUsageStats }. The repo is a cross-module dependency — injected into agent-memory routes (to log agent_memory_query/peer_memory_query) and consumed elsewhere for skill-usage analytics (getSkillUsageStats groups skill_usage rows by detail+agent_name).
5. Background Services
None as a timer. But log() performs a synchronous best-effort heartbeat tick (D1175): UPDATE agent_dispatch SET last_heartbeat=now WHERE agent_name=@agent AND status IN ('dispatched','active','silent_but_live'). Wrapped in try/catch — failure must not break the primary INSERT contract. NULL agent_name skips the tick (explicit JS guard mirroring SQL three-valued logic). Timeline activity event emitted via setImmediate after commit, with dispatch_id: null (activity is mission-scoped, not dispatch-scoped).
6. Data Contracts
ActivityEventType = cache_query|memory_search|agent_memory_query|peer_memory_query|skill_usage|tool_call|skill_gate (D1450/#48 added skill_gate). ActivityEvent: id, event_type, agent_name, mission_id, target_agent, detail, created_at. LogActivityInput: event_type (required), agent_name?, mission_id?, target_agent?, detail?. No Zod — validation is a Set.has(event_type) check + presence checks in the route (VALID_LOG_TYPES, symmetric on POST and the GET event_type filter).
7. Dependencies
- Gateway modules:
dispatch/eventBus.js(timelineBus— emitsactivityevents; also the reason activity ticks dispatch heartbeats via the shared DB). - External:
better-sqlite3,express. - Environment variables: none. Table
activity_log.
8. Test Coverage
| Layer | File | Notes |
|---|---|---|
| Routes | test/activity-routes.test.ts | VALID_LOG_TYPES 400 paths, event_type filter symmetry (D1450) |
| Heartbeat | test/activity-heartbeat-tick.test.ts | D1175 dispatch-heartbeat alignment, NULL agent skip, non-fatal failure |
9. Known Limitations
- No Zod validation — the
log_activityMCP tool and REST endpoint validate via manual destructure + aSet, so the tool cannot be safely converted to a raw twin without hardening the endpoint first (seemcpmodule tech-debt flag). New non-string fields would be exposed to the client-cache staleness class. - The heartbeat tick updates all in-flight rows for an agent, not a specific dispatch (activity has no
dispatch_idcolumn). If an agent has two concurrent in-flight dispatches, a singlelog_activityrefreshes both heartbeats — acceptable given the roster rarely double-dispatches one agent, but not surgically scoped. getSkillUsageStatsgroups on the free-textdetailcolumn (skill name); any inconsistency in how callers spell a skill name fragments its usage count.
10. Change History
| Date | Dispatch | Summary |
|---|---|---|
| 2026-07-04 | 2296 | Module spec authored (Phase 4 §5.2), smoke-clone-2, HEAD 5c9a304 |
| — | 1175 | AK-345 activity→dispatch heartbeat alignment tick |
| — | 1450 | #48 skill_gate event_type + filterable GET queries |