Skip to content

Commit 6d34d1b

Browse files
committed
fix: implement all 6 gap fixes from FEB_06_2025_GAP_ANALYSIS_PLAN.md
G1 (MEDIUM): Home dashboard Devices stat now shows actual count - Query devices count in gui_home() for user's agents - Pass devices_count to template context - Update home.html to display dynamic count G2 (MEDIUM): Main nav bar now includes all pages - Added Events, Memories, People to header nav - Added More dropdown for Audit, LiveKit Test, Artifacts - Added CSS styles for nav dropdown menu G3 (MINOR): Home Devices card shows dynamic device list - Query recent 5 devices in gui_home() - Render device list matching agents card pattern - Show empty state only when no devices exist G4 (MINOR): People/Consent now in Quick Actions - Added link G1 (MEDIUM): Home dashboard Devices stat now shows actual count - Query devices count in gui_home() for user's agents - Pass devices_count to template context - Update home.html to display dynamic count G2 (MEDIUM): Main nav bar now includes all pages - Added Events, Memories, People to header nav - Added More dropdow: cd /Users/jmajor/projects/daylily/marvain && git status --short 2>&1
1 parent 1344c04 commit 6d34d1b

File tree

7 files changed

+382
-9
lines changed

7 files changed

+382
-9
lines changed

FEB_06_2025_GAP_ANALYSIS_PLAN.md

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
# Gap Analysis — Marvain (2026-02-07)
2+
3+
**Branch:** `main` (post-merge of PR #31)
4+
**Auditor:** Forge (Augment Agent)
5+
**Method:** Full read-only review of all Python, SQL, JavaScript, HTML templates, and documentation.
6+
7+
---
8+
9+
## Executive Summary
10+
11+
**Overall completeness: ~97%** — Specs 0-5 are implemented end-to-end. The codebase has working pipelines for event ingestion → memory creation → action execution → real-time broadcast. All GUI pages exist with functional CRUD and WebSocket live updates.
12+
13+
**Gaps found: 6** (0 critical, 2 medium, 4 minor)
14+
15+
| # | Severity | Gap | Impact |
16+
|---|----------|-----|--------|
17+
| G1 | **MEDIUM** | Home dashboard Devices stat hardcoded to "—" | Dashboard shows wrong data |
18+
| G2 | **MEDIUM** | Main nav bar missing 5 pages | Users can't discover Events, Memories, People, Audit, LiveKit |
19+
| G3 | MINOR | Home Devices card always shows empty-state | No device preview like Agents card has |
20+
| G4 | MINOR | People/Consent page unreachable from any nav/link | Only accessible via direct URL `/people` |
21+
| G5 | MINOR | No device daemon onboarding UX | GUI-created devices show "Offline" with no guidance |
22+
| G6 | MINOR | README line 262 says worker is a "skeleton" | Outdated — worker is fully functional |
23+
24+
---
25+
26+
## Verified as COMPLETE (no gaps)
27+
28+
All items below were verified by tracing code from ingestion → storage → query → GUI rendering → WebSocket broadcast.
29+
30+
| Spec | Summary | Key Evidence |
31+
|------|---------|--------------|
32+
| Spec 0 | Identity spine: `agent_memberships`, `users.display_name`, `users.last_seen` | `sql/005`, 21 refs in `app.py` |
33+
| Spec 1 | Consent: `people`, `consent_grants`, privacy_mode enforcement | `app.py:1595-1730`, `planner:216-220` |
34+
| Spec 2 | Devices: legacy remotes fully removed, satellite daemon functional | PR #31 merged |
35+
| Spec 3 | Actions: proposal → approval → SQS → tool_runner → broadcast | `tool_runner:103-127`, `actions.html` |
36+
| Spec 4 | Context hydration: agent fetches events+memories on session start | `worker.py:256-271` |
37+
| Spec 5 | Broadcast: DynamoDB subscriptions → API Gateway Management API | `broadcast.py:99-166`, `marvain.js` |
38+
| Memory chain | Events → TranscriptQueue → Planner → memories w/embeddings → broadcast | `planner:305-326`, `template.yaml:514` |
39+
| Action chain | GUI approve → ActionQueue → ToolRunner → execute → broadcast | `tool_runner:127`, `actions.html:414-465` |
40+
| LiveKit test | Full WebRTC page with audio/video/chat/transcript/debug panel | `livekit_test.html` (1205 lines) |
41+
| Audit log | GUI viewer exists and renders entries from S3 | `app.py:gui_audit`, `audit.html` |
42+
43+
---
44+
45+
## Gap Details
46+
47+
### G1 — Home Dashboard Devices Stat Hardcoded (MEDIUM)
48+
49+
**What:** The Devices stat card on the home dashboard displays a literal `` dash instead of the actual device count.
50+
51+
**Evidence:**
52+
- `functions/hub_api/templates/home.html` line 18: `<div class="stat-value">—</div>` (hardcoded)
53+
- `functions/hub_api/app.py` lines 604-617: `gui_home()` passes `agents_count`, `spaces_count`, `pending_actions` to the template but **never queries or passes** `devices_count`
54+
- Compare to Agents: `"agents_count": len(agents_data)` (line 612) — works correctly
55+
- Compare to Spaces: `"spaces_count": len(spaces)` (line 613) — works correctly
56+
57+
**Fix:** Query device count for the user's agents and pass `devices_count` to the template. Update `home.html` line 18 to `{{ devices_count }}`.
58+
59+
**Effort:** ~15 lines of code.
60+
61+
---
62+
63+
### G2 — Main Nav Bar Missing 5 Pages (MEDIUM)
64+
65+
**What:** The header navigation (`base.html` lines 36-52) only links to: Home, Agents, Spaces, Devices, Actions. Five implemented pages are absent:
66+
67+
| Page | Route | Has GUI? | In Nav? | In Quick Actions? |
68+
|------|-------|----------|---------|-------------------|
69+
| Events | `/events` | ✅ Full page |||
70+
| Memories | `/memories` | ✅ Full page |||
71+
| People/Consent | `/people` | ✅ Full page |||
72+
| Audit | `/audit` | ✅ Full page |||
73+
| LiveKit Test | `/livekit-test` | ✅ Full page |||
74+
75+
**Impact:** Users must know URLs or find links buried in the home Quick Actions section. People/Consent has **zero** navigation links anywhere — it's only reachable by typing `/people` directly.
76+
77+
**Fix:** Add Events, Memories, and People to the main nav. Consider a "More" dropdown for Audit, LiveKit, Artifacts.
78+
79+
**Effort:** ~20 lines in `base.html`.
80+
81+
---
82+
83+
### G3 — Home Devices Card Always Shows Empty State (MINOR)
84+
85+
**What:** The home page Devices card (`home.html` lines 56-69) always renders an empty-state placeholder ("Manage your devices") regardless of how many devices exist. The Agents card (lines 71-103) dynamically lists up to 5 agents.
86+
87+
**Fix:** Query recent devices in `gui_home()` and render them like the agents list.
88+
89+
**Effort:** ~30 lines.
90+
91+
---
92+
93+
### G4 — People/Consent Page Has No Navigation Path (MINOR)
94+
95+
**What:** The People & Consent page (`/people`, `gui_people`) is not linked from:
96+
- Main nav bar
97+
- Home page Quick Actions section
98+
- Any other page
99+
100+
It is only reachable by typing `/people` in the browser. The page itself is fully functional (create people, manage consent grants).
101+
102+
**Fix:** Add to nav bar (G2 fix) and/or add to Quick Actions on home.
103+
104+
**Effort:** 1-2 lines.
105+
106+
---
107+
108+
### G5 — No Device Daemon Onboarding UX (MINOR)
109+
110+
**What:** When a user creates a device via the GUI, the device shows "Offline" because no satellite daemon is running for it. There is no guidance in the GUI on how to:
111+
1. Download/install the remote satellite daemon
112+
2. Configure it with the device token
113+
3. Start it
114+
115+
The satellite daemon (`apps/remote_satellite/daemon.py`) is fully functional — it just has no GUI-driven setup flow.
116+
117+
**Fix:** Add an onboarding panel on the device detail page with copy-pasteable setup commands (similar to the "Running an Agent Worker" collapsible in `livekit_test.html`).
118+
119+
**Effort:** ~40 lines of HTML.
120+
121+
---
122+
123+
### G6 — README Says Worker Is a "Skeleton" (MINOR)
124+
125+
**What:** `README.md` line 262: _"This repo does not ship a full satellite app yet; it ships a worker skeleton."_
126+
127+
This is outdated. The agent worker (`apps/agent_worker/worker.py`) is a fully functional LiveKit agent with:
128+
- OpenAI Realtime API integration
129+
- Transcript ingestion to Hub
130+
- Context hydration (events + memories)
131+
- Typed chat via data channel
132+
133+
**Fix:** Update the README paragraph to reflect current state.
134+
135+
**Effort:** 5 lines.
136+
137+
---
138+
139+
## Prioritized Implementation Plan
140+
141+
All 6 gaps can be fixed in a single focused session. No architectural changes needed.
142+
143+
### Phase 1: Dashboard Data (G1 + G3) — ~45 lines
144+
145+
1. In `gui_home()` (`app.py`): query device count and recent devices for the user's agents
146+
2. Pass `devices_count` and `recent_devices` to the template
147+
3. In `home.html`: replace hardcoded `` with `{{ devices_count }}`
148+
4. In `home.html`: replace devices empty-state with dynamic device list (matching agents card pattern)
149+
150+
### Phase 2: Navigation (G2 + G4) — ~25 lines
151+
152+
1. In `base.html`: add Events, Memories, People to header nav
153+
2. Consider a "More" dropdown or second row for Audit, LiveKit Test, Artifacts
154+
3. In `home.html`: add People/Consent to Quick Actions section
155+
156+
### Phase 3: Device Onboarding UX (G5) — ~40 lines
157+
158+
1. In `device_detail.html`: add collapsible "Getting Started" panel with:
159+
- Install instructions for the satellite daemon
160+
- Pre-filled command with the device token
161+
- Link to `apps/remote_satellite/README.md`
162+
163+
### Phase 4: Documentation (G6) — ~5 lines
164+
165+
1. Update `README.md` line 262 to accurately describe the agent worker
166+
167+
### Total Estimated Effort
168+
169+
| Phase | Lines Changed | Files |
170+
|-------|--------------|-------|
171+
| Phase 1 | ~45 | `app.py`, `home.html` |
172+
| Phase 2 | ~25 | `base.html`, `home.html` |
173+
| Phase 3 | ~40 | `device_detail.html` |
174+
| Phase 4 | ~5 | `README.md` |
175+
| **Total** | **~115** | **4 files** |
176+
177+
---
178+
179+
## What Was NOT Found (Claims Verified)
180+
181+
These items were explicitly checked and found to be **correctly implemented**, contradicting any assumption that they might be missing:
182+
183+
- ✅ Memory persistence (planner creates episodic + semantic memories with vector embeddings)
184+
- ✅ Action approval/rejection workflow (GUI buttons + API endpoints + SQS processing)
185+
- ✅ broadcast_fn wired in tool_runner (line 127: `broadcast_fn=_make_broadcast_fn(agent_id, space_id)`)
186+
- ✅ Context hydration in agent worker (fetches 50 events + 8 recalled memories)
187+
- ✅ Privacy mode blocks event processing in planner AND event ingestion in api_app
188+
- ✅ Consent grants CRUD (create, revoke-all-then-recreate pattern)
189+
- ✅ WebSocket real-time updates on all GUI pages (events, actions, memories, presence)
190+
- ✅ Device command channel (cmd.ping, cmd.run_action, cmd.config) via DynamoDB GSI
191+
- ✅ LiveKit test page with full audio/video/chat/transcript/debug panel
192+
- ✅ Audit trail viewer reading from S3 Object Lock bucket
193+
194+
---
195+
196+
## Methodology
197+
198+
1. Read every `.py`, `.sql`, `.html`, `.js` file in the repository
199+
2. Traced 3 end-to-end pipelines: transcript→memory, action→execution, event→broadcast
200+
3. Compared `GAPANALYSIS.md` claims against actual code (all claims verified)
201+
4. Compared `ADVANCED_FEATURE_PLAN.md` specs against implementation
202+
5. Checked every GUI template for functional completeness
203+
6. Checked nav bar and home page for discoverability of all features
204+
7. Checked `template.yaml` for SQS queue wiring correctness
205+
8. Verified all 18 CLI commands exist and are documented
206+
207+
---
208+
209+
**Awaiting review.** No code changes will be made until this analysis is approved.
210+

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,9 @@ Visit `http://localhost:8084/` — you'll be redirected to Cognito for login.
259259

260260
## Run the realtime agent worker (local)
261261

262-
This repo does not ship a full satellite app yet; it ships a worker skeleton.
262+
The agent worker is a fully functional LiveKit voice agent that connects to OpenAI's
263+
Realtime API, ingests transcripts to the Hub, and hydrates conversation context
264+
(recent events + recalled memories) on session start.
263265

264266
```bash
265267
cd apps/agent_worker

functions/hub_api/app.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,42 @@ def gui_home(request: Request) -> Response:
601601
for a in agents
602602
]
603603

604+
# Get devices count and recent devices for the user's agents
605+
devices_count = 0
606+
devices_data: list[dict] = []
607+
agent_ids = [str(a.agent_id) for a in agents]
608+
if agent_ids:
609+
try:
610+
placeholders = ", ".join(f":id{i}" for i in range(len(agent_ids)))
611+
params = {f"id{i}": aid for i, aid in enumerate(agent_ids)}
612+
count_rows = db.query(
613+
f"SELECT COUNT(*) as cnt FROM devices WHERE agent_id::TEXT IN ({placeholders})",
614+
params,
615+
)
616+
if count_rows:
617+
devices_count = count_rows[0].get("cnt", 0) or 0
618+
619+
dev_rows = db.query(
620+
f"""SELECT d.device_id::TEXT as device_id, d.name,
621+
a.name as agent_name, d.revoked_at
622+
FROM devices d
623+
JOIN agents a ON a.agent_id = d.agent_id
624+
WHERE d.agent_id::TEXT IN ({placeholders})
625+
ORDER BY d.created_at DESC LIMIT 5""",
626+
params,
627+
)
628+
for row in dev_rows:
629+
devices_data.append(
630+
{
631+
"device_id": str(row.get("device_id", "")),
632+
"name": row.get("name") or "Unnamed Device",
633+
"agent_name": row.get("agent_name", ""),
634+
"revoked": row.get("revoked_at") is not None,
635+
}
636+
)
637+
except Exception as e:
638+
logger.warning(f"Failed to fetch devices for home: {e}")
639+
604640
return templates.TemplateResponse(
605641
request,
606642
"home.html",
@@ -612,6 +648,8 @@ def gui_home(request: Request) -> Response:
612648
"agents_count": len(agents_data),
613649
"spaces_count": len(spaces),
614650
"pending_actions": pending_actions,
651+
"devices_count": devices_count,
652+
"devices": devices_data,
615653
**_get_ws_context(request),
616654
},
617655
)

functions/hub_api/static/css/marvain.css

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,56 @@ pre {
193193
text-decoration: none;
194194
}
195195

196+
/* Nav dropdown (More menu) */
197+
.nav-dropdown {
198+
position: relative;
199+
}
200+
.nav-dropdown-toggle {
201+
color: var(--color-gray-300);
202+
background: none;
203+
border: none;
204+
padding: var(--spacing-sm) var(--spacing-md);
205+
border-radius: var(--radius-sm);
206+
cursor: pointer;
207+
font: inherit;
208+
font-size: 0.875rem;
209+
transition: all 0.2s ease;
210+
}
211+
.nav-dropdown-toggle:hover,
212+
.nav-dropdown-toggle.active {
213+
color: var(--color-white);
214+
background: var(--color-secondary);
215+
}
216+
.nav-dropdown-menu {
217+
display: none;
218+
position: absolute;
219+
top: 100%;
220+
left: 0;
221+
min-width: 180px;
222+
background: var(--color-gray-800);
223+
border: 1px solid var(--color-gray-600);
224+
border-radius: var(--radius-md);
225+
padding: var(--spacing-xs) 0;
226+
z-index: 1000;
227+
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
228+
}
229+
.nav-dropdown:hover .nav-dropdown-menu {
230+
display: flex;
231+
flex-direction: column;
232+
}
233+
.nav-dropdown-menu a {
234+
color: var(--color-gray-300);
235+
padding: var(--spacing-sm) var(--spacing-md);
236+
transition: all 0.2s ease;
237+
white-space: nowrap;
238+
}
239+
.nav-dropdown-menu a:hover,
240+
.nav-dropdown-menu a.active {
241+
color: var(--color-white);
242+
background: var(--color-secondary);
243+
text-decoration: none;
244+
}
245+
196246
.header-actions {
197247
margin-left: auto;
198248
display: flex;

functions/hub_api/templates/base.html

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
<a href="{{ url_for('gui_home') }}" class="{% if active_page == 'home' %}active{% endif %}">
3838
<i class="fas fa-home"></i> Home
3939
</a>
40-
4140
<a href="{{ url_for('gui_agents') }}" class="{% if active_page == 'agents' %}active{% endif %}">
4241
<i class="fas fa-robot"></i> Agents
4342
</a>
@@ -50,6 +49,31 @@
5049
<a href="{{ url_for('gui_actions') }}" class="{% if active_page == 'actions' %}active{% endif %}">
5150
<i class="fas fa-tasks"></i> Actions
5251
</a>
52+
<a href="{{ url_for('gui_events') }}" class="{% if active_page == 'events' %}active{% endif %}">
53+
<i class="fas fa-stream"></i> Events
54+
</a>
55+
<a href="{{ url_for('gui_memories') }}" class="{% if active_page == 'memories' %}active{% endif %}">
56+
<i class="fas fa-brain"></i> Memories
57+
</a>
58+
<a href="{{ url_for('gui_people') }}" class="{% if active_page == 'people' %}active{% endif %}">
59+
<i class="fas fa-users"></i> People
60+
</a>
61+
<div class="nav-dropdown">
62+
<button class="nav-dropdown-toggle {% if active_page in ('audit', 'livekit_test', 'artifacts') %}active{% endif %}">
63+
<i class="fas fa-ellipsis-h"></i> More
64+
</button>
65+
<div class="nav-dropdown-menu">
66+
<a href="{{ url_for('gui_audit') }}" class="{% if active_page == 'audit' %}active{% endif %}">
67+
<i class="fas fa-shield-alt"></i> Audit Log
68+
</a>
69+
<a href="{{ url_for('gui_livekit_test') }}" class="{% if active_page == 'livekit_test' %}active{% endif %}">
70+
<i class="fas fa-video"></i> LiveKit Test
71+
</a>
72+
<a href="{{ url_for('gui_artifacts') }}" class="{% if active_page == 'artifacts' %}active{% endif %}">
73+
<i class="fas fa-archive"></i> Artifacts
74+
</a>
75+
</div>
76+
</div>
5377
</nav>
5478

5579
<div class="header-actions">

0 commit comments

Comments
 (0)