2 Commits

Author SHA1 Message Date
sb-iam c0fe424780 docs: add visual HTML demo runbook (ascii architecture + RSI-loop diagrams)
Self-contained one-pager: how-it-works ASCII diagrams (data flow + continual-
learning loop), the 3-min demo timeline, sponsor coverage, last-hour caveats
(suppression disabled, don't redeploy prod), and recovery. Demo-branch only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:11:27 -05:00
sb-iam 1a592abb39 docs: add final demo runbook (demo-branch only; not for main)
Reflects the current build: suppression disabled (Ramis), so the demo uses the
'Seen before.' escalate beat + learned_from edge, not the dead 'stays quiet' beat.
Kept on the demo branch so main stays clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 13:04:35 -05:00
13 changed files with 362 additions and 856 deletions
+30 -151
View File
@@ -7,55 +7,9 @@ each engineer is doing, remembers which interventions helped, and nudges the
team before duplicated work, merge collisions, or missed handoffs slow everyone
down.
<p align="center">
<a href="https://podman.live/">
<img
alt="Try PodMan live in production"
src="https://img.shields.io/badge/TRY%20PODMAN%20LIVE-https%3A%2F%2Fpodman.live-1f6feb?style=for-the-badge"
/>
</a>
</p>
<h2 align="center">
<a href="https://podman.live/">Open the live production app: https://podman.live/</a>
</h2>
<p align="center">
PodMan is already deployed. Click the link above and try the production app.
</p>
<h2 align="center">
<a href="https://youtu.be/bWJIsIWTgr0">Watch the demo video</a>
</h2>
<p align="center">
<a href="https://youtu.be/bWJIsIWTgr0">
<img
src="https://img.youtube.com/vi/bWJIsIWTgr0/maxresdefault.jpg"
alt="PodMan demo video thumbnail"
width="760"
/>
</a>
</p>
[LiveKit](https://livekit.io/) · [MongoDB](https://www.mongodb.com/) ·
[Gemini](https://ai.google.dev/) · [Hermes](https://hermes-agent.nousresearch.com/) ·
[Modular MAX](https://www.modular.com/max) ·
[DigitalOcean](https://www.digitalocean.com/)
<p align="center">
<img
src="docs/assets/interruption-flow-recovery.jpg"
alt="A chart showing that a five minute interruption can create a much longer recovery period before a developer returns to flow state."
width="760"
/>
</p>
> PodMan exists to prevent the five-minute interruption from becoming a
> half-hour recovery tax.
Source image: `~/pic.jpg`, committed as
`docs/assets/interruption-flow-recovery.jpg` so it renders on GitHub.
[vLLM](https://vllm.ai/) · [DigitalOcean](https://www.digitalocean.com/)
## Read This First
@@ -65,68 +19,10 @@ Source image: `~/pic.jpg`, committed as
| Check the app | `curl https://podman.live/health` |
| Check pods and memory | `curl https://podman.live/api/pods && curl https://podman.live/api/memory/stats` |
| Use the LLM externally | Base URL `https://llm.alhinai.dev/v1`, model `gemma-4-31B-it` |
| Test Hermes | `hermes -z 'Reply with exactly: working' --provider gemma4-31b-max --model gemma-4-31B-it` |
| Test Hermes | `hermes -z 'Reply with exactly: working' --provider gemma4-31b-vllm --model gemma-4-31B-it` |
| Start local development | API, vision agent, and frontend commands are in [Local Development](#local-development) |
| Debug production | Public checks first, then systemd services in [Production Operations](#production-operations) |
## Hackathon Fit
PodMan is built for the **2026 AI Engineer World's Fair Hackathon** theme:
**Continual Learning**.
It is not a wrapper chatbot or static dashboard. It is a production-running
agentic coordination system that gets better from real use: every observation,
collision, intervention, accept, dismiss, suppression, and conversation note
feeds MongoDB-backed memory so future interventions become sharper and less
annoying.
| Hackathon target | How PodMan addresses it |
| ---------------------- | ---------------------------------------------------------------------------------------- |
| Continual Learning | Learns from real team behavior and accepted/dismissed interventions. |
| Self-Improvement Stack | Uses memory stats, Hermes jobs, production watchdogs, and deployment checks. |
| Recursive Intelligence | Gives agents operational memory and feedback loops for improving future coordination. |
| Live Demo | Already deployed at [`https://podman.live/`](https://podman.live/). |
| Technicality | Combines realtime media, vision, vector recall, graph traversal, memory, and ops agents. |
| Creativity | Solves the coordination cost around AI-assisted engineering teams. |
### Sponsor Tech Used
| Sponsor / tech | Usage in PodMan |
| -------------- | -------------------------------------------------------------------------------- |
| DigitalOcean | Hosts the production app, API, workers, Caddy, and systemd-supervised services. |
| LiveKit | Realtime room layer for screen share, audio, data messages, and agent presence. |
| Gemini | Screen understanding, live room conversation, urgent TTS, embeddings, and music. |
| MongoDB Atlas | Durable memory, `$vectorSearch`, `$graphLookup`, user learning, and job logs. |
| Modular MAX | Serves `gemma-4-31B-it` as the long-context reasoning model. |
### What To Try
```mermaid
flowchart LR
A["Open podman.live"] --> B["Join a pod"]
B --> C["Share screen via LiveKit"]
C --> D["Gemini extracts work context"]
D --> E["MongoDB recalls prior outcomes"]
E --> F["PodMan nudges only when useful"]
F --> G["Accept or dismiss"]
G --> H["Memory improves next run"]
classDef action fill:#e8f1ff,stroke:#3366cc,color:#0b1f44;
classDef ai fill:#f4edff,stroke:#805ad5,color:#2d1857;
classDef memory fill:#fff6df,stroke:#c47f00,color:#3d2b00;
class A,B,C,F,G action;
class D ai;
class E,H memory;
```
1. Open [`https://podman.live/`](https://podman.live/).
2. Create or join a pod.
3. Add teammates and share screens.
4. Watch PodMan build live context, detect overlap, and surface intervention
cards.
5. Accept or dismiss a card; that feedback becomes memory for the next similar
event.
---
## Current Live System
@@ -137,10 +33,10 @@ flowchart LR
| API | `https://podman.live/api/*` | Pods, tokens, outcomes, memory |
| Health | `https://podman.live/health` | Backend readiness |
| Local API | `127.0.0.1:8787` | Express service behind Caddy |
| Reasoning LLM | `https://llm.alhinai.dev/v1` | OpenAI-compatible Modular/MAX endpoint |
| Reasoning LLM | `https://llm.alhinai.dev/v1` | OpenAI-compatible Gemma/vLLM endpoint |
| API key | `not-needed` | Placeholder key for OpenAI clients |
| Hermes model | `gemma-4-31B-it` | 262K-context tool-using agent |
| Hermes config | `gemma4-31b-max` | Custom provider used by Hermes locally |
| Hermes config | `gemma4-31b-vllm` | Custom provider used by Hermes locally |
```mermaid
flowchart TB
@@ -170,8 +66,8 @@ flowchart TB
subgraph Reasoning["External Reasoning Endpoint"]
Tunnel["Cloudflare Tunnel<br/>llm.alhinai.dev"]
MaxServe["MAX OpenAI Server<br/>gemma-4-31B-it<br/>262144 context"]
Hermes["Hermes Agent<br/>provider: gemma4-31b-max"]
VLLM["vLLM OpenAI Server<br/>gemma-4-31B-it<br/>262144 context"]
Hermes["Hermes Agent<br/>provider: gemma4-31b-vllm"]
end
Browser --> Caddy --> API
@@ -188,8 +84,7 @@ flowchart TB
Voice --> GeminiVoice
Vision --> GeminiEmbed
Voice --> GeminiEmbed
Ops --> Hermes --> Tunnel --> MaxServe
Tunnel -. "public route<br/>llm.alhinai.dev/v1" .-> MaxServe
Ops --> Hermes --> Tunnel --> VLLM
API --> Hermes
Lyria --> Voice
@@ -200,7 +95,7 @@ flowchart TB
class Browser,Room user;
class Caddy,API,Vision,Voice,Ops app;
class Observations,State,Outcomes data;
class GeminiVision,GeminiVoice,GeminiEmbed,Lyria,Tunnel,MaxServe,Hermes ai;
class GeminiVision,GeminiVoice,GeminiEmbed,Lyria,Tunnel,VLLM,Hermes ai;
```
## What It Does
@@ -243,7 +138,7 @@ flowchart LR
VisionRoute --> Gemini20["gemini-2.0-flash<br/>screen understanding"]
ConversationRoute --> GeminiLive["gemini-3.1-flash-live-preview<br/>live Q&A"]
ConversationRoute --> GeminiTTS["gemini-3.1-flash-tts-preview<br/>urgent speech"]
HermesRoute --> Gemma["gemma-4-31B-it<br/>served by Modular MAX"]
HermesRoute --> Gemma["gemma-4-31B-it<br/>Hermes + vLLM + tools"]
EmbedRoute --> Voyage["voyage-4-lite<br/>primary embeddings"]
EmbedRoute --> GeminiEmbed["gemini-embedding-001<br/>fallback embeddings"]
MusicRoute --> Lyria["lyria-3-clip-preview<br/>background music"]
@@ -256,7 +151,7 @@ flowchart LR
class Gemini20,GeminiLive,GeminiTTS,Gemma,Voyage,GeminiEmbed,Lyria model;
```
### Gemma 4 31B via Modular MAX
### Gemma 4 31B via vLLM
Hermes uses the external OpenAI-compatible endpoint:
@@ -267,16 +162,17 @@ Model: gemma-4-31B-it
Context: 262144 tokens
```
The Modular/MAX server is configured for long-context Gemma 4 serving:
The vLLM server is configured for Hermes-style tool use:
```text
--max-length 262144
--device-memory-utilization 0.85
--kv-cache-format float8_e4m3fn
--enable-prefix-caching
--max-model-len 262144
--max-num-seqs 1
--max-num-batched-tokens 16384
--gpu-memory-utilization 0.85
--kv-cache-dtype fp8
--enable-auto-tool-choice
--tool-call-parser gemma4
--enable-chunked-prefill
--max-batch-size 1
--max-batch-input-tokens 16384
```
Hermes should point at that endpoint with this provider shape:
@@ -284,11 +180,11 @@ Hermes should point at that endpoint with this provider shape:
```yaml
model:
default: gemma-4-31B-it
provider: gemma4-31b-max
provider: gemma4-31b-vllm
providers:
gemma4-31b-max:
name: Gemma 4 31B Modular MAX (256K)
gemma4-31b-vllm:
name: Gemma 4 31B vLLM (256K)
api: https://llm.alhinai.dev/v1
api_key: not-needed
transport: chat_completions
@@ -303,25 +199,8 @@ agent:
```
Why this matters: Hermes sends OpenAI tool schemas and `tool_choice: "auto"`.
The model endpoint must support automatic tool choice so Hermes can initialize
the agent without a client-side workaround.
### Modular MAX
Gemma 4 is served through Modular MAX on the GB10 machine.
| MAX support | Value |
| ------------ | ---------------------------------------- |
| Architecture | `Gemma4ForConditionalGeneration` |
| Example | `google/gemma-4-31B-it` |
| Encodings | `float4_e2m1fnx2`, `float16`, `bfloat16` |
| Runtime | Role |
| ----------------- | ----------------------------------------- |
| Modular MAX | OpenAI-compatible Gemma 4 serving runtime |
| `gemma-4-31B-it` | Primary long-context reasoning model |
| `262144` tokens | Reported model context window |
| `llm.alhinai.dev` | Public Cloudflare-routed model endpoint |
Without `--enable-auto-tool-choice` and `--tool-call-parser gemma4`, vLLM returns
HTTP 400 before Hermes can initialize an agent.
### Gemini Surfaces
@@ -372,7 +251,7 @@ observe -> detect -> recall prior outcomes -> policy gate -> act -> record outco
| Vision agent | Node + `@livekit/rtc-node` | Subscribes to screen tracks, samples frames, publishes interventions |
| Live voice agent | Python LiveKit Agents + Gemini Live | Real-time voice Q&A in a pod room |
| Memory | MongoDB Atlas | Observations, engineer state, collisions, interventions, outcomes |
| Reasoning agent | Hermes + Gemma Modular/MAX | Tool-using autonomous assistant and ops layer |
| Reasoning agent | Hermes + Gemma vLLM | Tool-using autonomous assistant and ops layer |
| Realtime transport | LiveKit Cloud | Screen tracks, audio tracks, data messages |
| Hosting | DigitalOcean + Caddy + systemd | Static frontend, API, workers, watchdog timers |
@@ -554,7 +433,7 @@ pnpm hermes:sync-deploy
pnpm deploy:doctor:strict
```
Gemma Modular/MAX endpoint checks:
Gemma/vLLM endpoint checks:
```bash
curl https://llm.alhinai.dev/v1/models \
@@ -602,7 +481,7 @@ POD_ROOM=demo-pod
Gemma/Hermes provider values live in Hermes config, not PodMan `.env`:
```text
provider: gemma4-31b-max
provider: gemma4-31b-vllm
model: gemma-4-31B-it
base_url: https://llm.alhinai.dev/v1
api_key: not-needed
@@ -635,7 +514,7 @@ For Hermes/Gemma:
```bash
hermes -z 'Reply with exactly: working' \
--provider gemma4-31b-max \
--provider gemma4-31b-vllm \
--model gemma-4-31B-it
```
@@ -650,14 +529,14 @@ working
## Troubleshooting
| Symptom | Likely cause | Check |
| ----------------------------- | ------------------------------------------------ | ---------------------------------------------------- |
| ---------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------ |
| Frontend loads but API fails | Backend or Caddy proxy issue | `systemctl status podman-platform-api caddy` |
| `/api/*` returns 502 | API not listening on `8787` | `ss -ltnp`, `curl http://127.0.0.1:8787/health` |
| No screen observations | Vision agent not in LiveKit room | `journalctl -u podman-platform-agent -n 80` |
| Live voice missing | Python conversation agent down | `journalctl -u podman-live-conversation-agent -n 80` |
| Memory empty | MongoDB unavailable or env missing | `curl /api/memory/stats`, backend logs |
| Hermes tool calls fail | Modular/MAX endpoint does not accept tool schema | verify Hermes provider and `/v1/chat/completions` |
| `llm.alhinai.dev` returns 502 | Gemma server still loading or tunnel target down | `curl /v1/models`, MAX logs on Gemma host |
| Hermes says tool auto-choice is disabled | vLLM missing tool flags | verify `--enable-auto-tool-choice --tool-call-parser gemma4` |
| `llm.alhinai.dev` returns 502 | Gemma vLLM still loading or tunnel target down | `curl /v1/models`, vLLM logs on Gemma host |
---
Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

+56
View File
@@ -0,0 +1,56 @@
# PodMan — Demo Runbook (final, read-off)
> One page. Reflects the **current live state** (`podman.live`) as of the final hour.
> Companion to `docs/demo.md` (full 4-min script) — this is the de-risked version.
## ⛔ Read first — last-hour changes that affect the demo
1. **Suppression is DISABLED for the demo** (`shouldIntervene` only filters `info`).
Every real collision now surfaces — good (no accidental silencing). **But the
`docs/demo.md` 2:10 beat "it learned to stay QUIET / no card fires" will NOT
work.** Do the **escalate** half only (below). Do not promise silence on stage.
2. **Do NOT redeploy prod.** The live build has demo code not on `main`
(`userPodContext`); redeploying `main` would remove it *and* surface stale
"SUPPRESSED" beats. If the auto-deploy timer is on, stop it before the run:
`systemctl stop podman-hermes-sync-deploy.timer`.
3. **Never debug on stage.** Narrate, fall back to the backup recording, keep moving.
## ✅ Pre-flight (do once, before you start)
- [ ] `curl https://podman.live/health``{"ok":true}`
- [ ] Deploy timer stopped (above); confirm exactly one agent: `systemctl status podman-platform-agent`
- [ ] Browser open on the pod; **click "Enable audio"** in-room (TTS needs it)
- [ ] Backup recording cued on a second device
- [ ] Two screen-shares live (the two "engineers")
## 🎬 The 3-minute path (the coherent, working story)
| Time | Do | Say (land the value) |
|---|---|---|
| **0:00** | Pod view, two live screen tiles | "Coordination is the bottleneck now, not coding. PodMan watches everyone's work live — no one has to ask 'what are you on?'" |
| **0:30** | Point at tiles; show activity stream filling | "Real screen-shares over **LiveKit**; each frame → **Gemini Vision** returns file/symbol/activity — a perception layer, not a chatbot." |
| **1:05** | Two engineers edit the **same file**, unpushed | "GitHub can't see this — nothing's pushed. We fuse live screen context with **local git truth**." → **collision card** appears. |
| **1:40** | **Accept** the card | Real **GitHub sync-PR** is created + outcome recorded. "One tap closes the loop." |
| **2:10** | **Trigger the same collision again** | Card now says **"Seen before."** and escalates straight to the **Gemini TTS voice** cue. "It recalled the prior event from **MongoDB Atlas vector search** and escalated — that's the learning." |
| **2:35** | Cut to **GraphView** | "This graph is materialized **live from MongoDB right now** — and here's the **`learned_from` edge**: PodMan learned who owns this file from the accepted outcome. Not a mock." |
| **3:00** | Stack + close | "All on **DigitalOcean**, systemd-supervised; ambient score is **Gemini Lyria**. Coordination awareness, collisions caught before they cost an afternoon, memory that sharpens each session." |
## 🗣️ Sponsor coverage (say each ≥ once)
- **Gemini** — Vision (0:30), TTS voice (2:10), Lyria (3:00) — add Live conversation if time allows
- **LiveKit** — "real screen-shares over LiveKit" (0:30), TTS over LiveKit (2:10)
- **MongoDB** — "Atlas vector-search recall" + "graph materialized live from MongoDB" (2:10, 2:35)
- **DigitalOcean** — "all on DigitalOcean, systemd-supervised" (3:00)
## 🚑 If it breaks
| Failure | Recovery |
|---|---|
| Voice doesn't fire | Cut to the card, say the line aloud — cards are the default path |
| Collision won't trigger | Use the backup recording for that beat; keep narrating |
| Graph looks empty/odd | Reload once; if still off, narrate from the recording |
| Agent silent | Suspect a duplicate `podman-hermes` process, not the UI — but **don't fix on stage** |
## Optional (only if ahead of time): the five-minute-meeting killer
Open the live voice conversation; ask *"PodMan, what is everyone working on, and where is the collision detector?"* → answers via real tool calls (**Gemini Live API**). Skip entirely if tight on time or if it drops.
---
**The spine:** live awareness → collision caught pre-push → accept → **"Seen before." + voice + `learned_from` edge**. That's the win. Suppression/"stays quiet" is intentionally out this run.
+32 -107
View File
@@ -1,92 +1,31 @@
# PodMan — Demo Scripts
# PodMan — 4-Minute Demo Script
**Theme:** Continual Learning. Two scripts below: a **1-minute live script**
(§ first) for showing real-time interventions fast with teammates, and the full
**4-minute script** for the complete story. Practice the 4-min to land at 3:45.
**Theme:** Continual Learning. **Hard limit:** 4:00. Practice to land at 3:45.
**The thesis (say this first, in either script):** AI is collapsing the cost of
*writing* code. More and more of every codebase is authored with AI assist — and
increasingly by **autonomous agents**. So the bottleneck of software engineering
is shifting away from engineering itself toward **organization, management, and
project coordination**. Pair a team with hundreds of agents all committing to the
same repo at once and it becomes **physically impossible for a human to track
progress or avoid stepping on someone else's work.** Code generation scaled;
human coordination did not. That gap is the new bottleneck.
**The one-line story:** writing code isn't the bottleneck anymore — *coordinating
who (and what) is writing what* is. PodMan is a pair programmer for the whole
team — humans **and** agents: it watches every actor's work in real time, gives
everyone live status without anyone having to interrupt anyone, catches collisions
before they land, and learns your team's dynamics so it nudges less and helps more
over time.
**The one-line story:** writing code isn't the bottleneck anymore — _coordinating
who's writing what_ is. PodMan is a pair programmer for the whole team: it watches
every member's work in real time, gives everyone live status without anyone having
to interrupt anyone, and learns your team's dynamics so it nudges less and helps
more over time.
**The hook to land:** a "quick five-minute question" actually costs ~25 minutes of
lost focus — for two people. Now multiply that across a team plus a swarm of
agents nobody can watch. PodMan removes the reason to ask and surfaces the
collision no human could have caught in time. That recovery time, saved across
every actor, every day, is the value.
lost focus — for two people. PodMan removes the reason to ask. Multiply the saved
recovery time across every teammate, every day, and that is the value.
---
## 1-Minute Live Script (you + teammates — real-time interventions)
**Hard limit 1:00.** Hits the tracks: real-time multimodal, on-device Gemma,
Continual Learning, Self-Improvement, Recursive Intelligence.
**Pre-flight:** pod open on `podman.live`, all joined, screen share + **sound on**;
git watchers running; everyone has `README.md` open. On-device **Gemma** must be
up as Hermes's voice (else Gemini TTS auto-falls-back — just drop the "on-device"
word).
**0:000:12 — Thesis**
> "Code is almost free to write now — humans with AI, soon swarms of agents, all
> on one repo. No human can track that. The bottleneck is coordination — and it
> has to fix itself, live. Watch."
**0:120:35 — Collision caught live (money shot)**
- You + a teammate both edit `README.md`, save (unpushed).
- Card appears on every screen + voice fires: _"<you> and <teammate> are both
editing README.md. Sync before pushing."_
- Say: "Gemini Vision reads our screens, fused with local git, in real time —
and the alert is **spoken by a Gemma model on-device**. Neither of us asked."
**0:350:52 — Self-improving (narrate)**
- Third teammate edits too → **new** card + voice for the new pair (never goes
silent).
- Say: "Every trace lands in **Atlas**; vector search recalls past collisions,
repeats come back tagged **'Seen before'** and escalate — no retraining. It
tunes its own coordination from its own outcomes."
**0:521:00 — Close**
> "Real-time awareness, on-device voice, a coordination layer that improves
> itself — for humans and agents, zero interruptions."
_Fallback:_ Gemma down → drop the on-device line. Voice silent → read it, point
at the card. No card → switch the pair to a fresh file and re-save.
---
## The script (4:00)
### 0:000:30 — The problem + hook
> "AI made writing code almost free — humans with AI today, swarms of autonomous
> agents tomorrow, all committing to the same repo. The bottleneck stopped being
> engineering and became coordination: checking each other's work, re-planning
> collisions, the constant 'what are you working on?' At agent scale no human can
> even track it. A five-minute question already costs both people 25 minutes of
> lost focus. PodMan is a pair programmer for the whole team — humans and agents:
> it watches everyone's work live, so anyone sees another's status without
> interrupting them, catches collisions before they land, and learns your team as
> it goes."
> "AI made writing code easy. The thing still slowing teams down is coordination
> — checking each other's work, re-planning collisions, and the constant 'what
> are you working on?' A five-minute question really costs both people 25 minutes
> of lost focus. PodMan is a pair programmer for the whole team: it watches
> everyone's work live, so anyone can see another's status without interrupting
> them — and it learns your team as it goes."
*On screen:* the pod view, two teammates joined, screen-share tiles live.
_On screen:_ the pod view, two teammates joined, screen-share tiles live.
### 0:301:05 — Real-time team awareness (LiveKit + Gemini Vision)
@@ -96,10 +35,10 @@ at the card. No card → switch the pair to a fresh file and re-save.
symbol, activity — not a chatbot, a perception layer."
- Show the live activity stream filling in (Signals vs Reasoning sections).
- Land the value: "This is the part that replaces 'what are you working on?' —
every teammate's current work is just *visible*, in real time. Nobody had to
every teammate's current work is just _visible_, in real time. Nobody had to
ask."
*Built-by-us callout:* `backend/src/vision/gemini.ts`, the LiveKit agent worker.
_Built-by-us callout:_ `backend/src/vision/gemini.ts`, the LiveKit agent worker.
### 1:051:40 — The catch (detection + first intervention)
@@ -107,27 +46,27 @@ ask."
- "Normally nobody notices until merge time. GitHub can't see this — nothing's
pushed. Our detector fuses live screen context with **local git truth** from a
watcher on each laptop."
- A collision card appears: *"alice + bob both on detector.ts (unpushed)."*
- Let the **Gemini TTS** urgent voice fire once over LiveKit: *"alice and bob are
both editing detector.ts. Please sync before pushing."*
- A collision card appears: _"alice + bob both on detector.ts (unpushed)."_
- Let the **Gemini TTS** urgent voice fire once over LiveKit: _"alice and bob are
both editing detector.ts. Please sync before pushing."_
- Land the value: "That's a merge conflict and a wasted afternoon caught before it
happened — and neither of them had to be tracking the other."
*Built-by-us callout:* `collision/detector.ts`, `action/hermes.ts`,
_Built-by-us callout:_ `collision/detector.ts`, `action/hermes.ts`,
`voice/live.ts`.
### 1:402:10 — Cross-channel overlap (research + code)
- Keep alice editing `livekit.py`.
- Have bob share a browser tab on LiveKit docs/SDK pages.
- A collaboration nudge appears: *"🤝 bob is researching LiveKit agents
- A collaboration nudge appears: _"🤝 bob is researching LiveKit agents
(docs.livekit.io) while alice edits livekit.py — sync up before duplicating
effort."*
effort."_
- Land the value: "This is not a merge conflict. PodMan caught duplicated effort
across channels — code on one screen, research on another — and nudged the team
before two people solved the same problem twice."
*Built-by-us callout:* `vision/gemini.ts`, `collision/research.ts`,
_Built-by-us callout:_ `vision/gemini.ts`, `collision/research.ts`,
`memory/vectors.ts`.
### 2:102:50 — Continual learning (the theme — the money shot)
@@ -146,15 +85,15 @@ This is the differentiator. Two beats, both from pre-seeded memory:
that adapts on the recalled outcome."
- Optional: show `/api/memory/stats` counts climbing — accumulated experience.
*Built-by-us callout:* `memory/vectors.ts` ($vectorSearch), `memory/policy.ts`
_Built-by-us callout:_ `memory/vectors.ts` ($vectorSearch), `memory/policy.ts`
(outcome-conditioned gate), `memory/store.ts`.
### 2:503:30 — The five-minute meeting, killed (Gemini Live API)
- Frame it: "Instead of breaking a teammate's focus to ask what they're up to,
you ask PodMan."
- Open the live voice conversation. Ask out loud: *"PodMan, what is everyone
working on, and where is the collision detector implemented?"*
- Open the live voice conversation. Ask out loud: _"PodMan, what is everyone
working on, and where is the collision detector implemented?"_
- It answers with **real tool calls**`search_repo`, git history, current
collisions — not guesses.
- "This is the **Gemini Live API**, streaming speech-to-speech over LiveKit, with
@@ -162,31 +101,25 @@ custom function tools we wrote so it grounds every answer in the actual repo
and live state. That's the status sync, answered in seconds, with zero recovery
tax on anyone else."
*Built-by-us callout:* `agents/podman-live-conversation/agent.py`.
_Built-by-us callout:_ `agents/podman-live-conversation/agent.py`.
### 3:303:50 — Stack + close
- "All on **DigitalOcean** — static frontend, API, and agent workers, supervised
by systemd. The ambient score is **Gemini Lyria** generated per pod through the
Interactions API."
- Close: "Engineering ability stopped being the bottleneck — coordination is, and
it only gets worse as agents start writing alongside us. PodMan gives a whole
team, humans and agents, real-time awareness without the interruptions, catches
- Close: "Engineering ability stopped being the bottleneck — coordination is.
PodMan gives a whole team real-time awareness without the interruptions, catches
collisions before they cost an afternoon, and learns each team's dynamics so it
helps more over time. Saved focus, multiplied across every actor. That's
helps more over time. Saved focus, multiplied across every teammate. That's
continual learning, shipped."
### 3:504:00 — Buffer / Q&A handoff
---
## Sponsor-prize coverage (say each at least once)
| Prize | Spoken moment | Segment |
| ---------------- | -------------------------------------------------------------------------------- | ---------------------- |
| **Gemini** | Vision perception, Live API agent w/ tools, TTS voice, Lyria score | 0:30, 1:05, 2:50, 3:30 |
@@ -194,14 +127,10 @@ continual learning, shipped."
| **MongoDB** | "Atlas vector search recalling past events" | 1:50 |
| **DigitalOcean** | "all on DigitalOcean, systemd-supervised workers" | 3:30 |
---
## If something breaks (live recovery)
| Failure | Recovery |
| ----------------------- | ----------------------------------------------------------------------- |
| Voice doesn't fire | Cut to the card; say the line aloud; cards are the default path anyway. |
@@ -209,16 +138,12 @@ continual learning, shipped."
| Collision won't trigger | Use the backup recording for that beat; keep narrating. |
| Agent flapping | Pre-checked — but if so, `systemctl restart podman-platform-agent`. |
**Rule:** never debug on stage. Narrate, fall back to recording, keep moving.
---
## Tight timing summary
| Time | Beat |
| ---- | ---------------------------------------------------------- |
| 0:00 | Problem (coordination cost) + hook + original-work line |
+189
View File
@@ -0,0 +1,189 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>PodMan — Demo Runbook</title>
<style>
:root{
--bg:#0b0f17; --panel:#121826; --panel2:#0e1421; --ink:#e6edf6; --muted:#9fb0c3;
--line:#23304a; --blue:#5b9bff; --violet:#a78bfa; --red:#ff6b6b; --amber:#f4b942;
--green:#43d39e; --slate:#8aa0b8;
}
*{box-sizing:border-box}
body{margin:0;background:radial-gradient(1200px 600px at 70% -10%,#15203a 0,var(--bg) 55%);
color:var(--ink);font:15px/1.55 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}
.wrap{max-width:960px;margin:0 auto;padding:32px 22px 80px}
header{border-bottom:1px solid var(--line);padding-bottom:18px;margin-bottom:26px}
h1{font-size:30px;margin:0 0 6px;letter-spacing:-.3px}
h1 .dot{color:var(--green)}
.sub{color:var(--muted);font-size:15px;max-width:680px}
h2{font-size:13px;text-transform:uppercase;letter-spacing:.14em;color:var(--blue);
margin:34px 0 12px;border-left:3px solid var(--blue);padding-left:10px}
pre{background:var(--panel2);border:1px solid var(--line);border-radius:10px;
padding:16px 18px;overflow:auto;color:#cfe0f5;font:12.5px/1.45 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
.grid{display:grid;gap:14px}
.card{background:linear-gradient(180deg,var(--panel),var(--panel2));border:1px solid var(--line);
border-radius:12px;padding:16px 18px}
table{width:100%;border-collapse:collapse;font-size:14px}
th,td{text-align:left;padding:9px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.06em}
td.t{white-space:nowrap;color:var(--violet);font-variant-numeric:tabular-nums;font-weight:600}
.say{color:var(--green)}
.pill{display:inline-block;padding:1px 8px;border-radius:999px;font-size:11px;font-weight:700;letter-spacing:.04em}
.p-blue{background:rgba(91,155,255,.16);color:var(--blue)} .p-violet{background:rgba(167,139,250,.16);color:var(--violet)}
.p-green{background:rgba(67,211,158,.16);color:var(--green)} .p-amber{background:rgba(244,185,66,.16);color:var(--amber)}
.warn{background:rgba(255,107,107,.08);border:1px solid rgba(255,107,107,.4);border-radius:12px;padding:14px 18px}
.warn h3{margin:0 0 8px;color:var(--red);font-size:14px;letter-spacing:.02em}
ol{margin:0;padding-left:20px} ol li{margin:6px 0}
code{background:#0a1220;border:1px solid var(--line);border-radius:5px;padding:1px 6px;color:#bcd4f2;font-size:12.5px}
.muted{color:var(--muted)} .spine{color:var(--green);font-weight:600}
footer{margin-top:40px;color:var(--muted);font-size:12.5px;border-top:1px solid var(--line);padding-top:14px}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>PodMan <span class="dot"></span> Demo Runbook</h1>
<div class="sub">A pair programmer for the whole team. It watches everyone's work live, catches
collisions <em>before the push</em> (when GitHub still can't see them), and learns the team's
dynamics so it nudges less and helps more over time.</div>
</header>
<div class="card">
<strong>The 10-second story:</strong> <span class="muted">Coding isn't the bottleneck anymore —
<em>coordination</em> is. PodMan gives a team real-time awareness with zero interruptions, catches
the pre-push collision that costs an afternoon, and the Team-Memory Graph proves it learned —
materialized live from MongoDB, not a mock.</span>
</div>
<h2>How it works — architecture</h2>
<pre>
░ PodMan data flow ░
┌──────────────┐ screen-share track ┌────────────────────────────┐
│ Engineers │ ──────────────────────▶ │ LiveKit Cloud │
│ (browsers) │ ◀───────────────────────│ room = "demo-pod" │
└──────┬───────┘ cards · voice · cues └─────────────┬──────────────┘
│ (data channel) │ frames @ ~1 fps
│ live graph (poll 5s) │ + local git truth
│ ▼
│ ┌────────────────────────────────────┐
│ │ HERMES AGENT (Node, podman-hermes)│
│ │ frame ─▶ GEMINI VISION (file/ │
│ │ symbol/activity) │
│ │ fuse with git watcher (unpushed!) │
│ │ ─▶ detect same-file collision │
│ │ ─▶ Gemini TTS voice over LiveKit │
│ └──────────────────┬───────────────────┘
│ │ read / write
┌──────┴────────────┐ materialize live ┌───────────▼──────────────────┐
│ TEAM-MEMORY │ ◀──────────────────────│ MongoDB Atlas │
│ GRAPH (SVG) │ graph/live.ts │ observations · collisions │
│ owns/edits/ │ │ interventions · outcomes │
│ collides/ │ │ team_model · graph_nodes/edges│
│ learned_from │ │ + $vectorSearch recall │
└───────────────────┘ └───────────────────────────────┘
Infra: DigitalOcean droplet · systemd-supervised API + agent · Caddy static frontend
Models: Gemini (Vision · TTS · Live voice · embeddings · Lyria ambient score)
</pre>
<h2>How it works — the continual-learning loop</h2>
<pre>
observe ──▶ store ──▶ predict ──▶ outcome ──▶ adapt
│ │ │ │ │
Gemini MongoDB same-file accept / learned_from
Vision + records overlap dismiss edge + owner
git truth detected (1 tap) persists
▲ │
└───────────────── recall ($vectorSearch) ◀─────────┘
"Seen before." → escalates on a repeat
The proof: one accept/dismiss tap — no retraining, no labels — changes the
NEXT run. Run 2 of the same collision says "Seen before." and a violet
learned_from edge appears on the graph. That edge is read straight from Atlas.
</pre>
<h2>The 3-minute demo (read off this)</h2>
<table>
<thead><tr><th>Time</th><th>Do</th><th>Say — land the value</th></tr></thead>
<tbody>
<tr><td class="t">0:00</td><td>Pod view, two live screen tiles</td>
<td class="say">"Coordination is the bottleneck now, not coding. PodMan watches everyone's work live — nobody has to ask 'what are you on?'"</td></tr>
<tr><td class="t">0:30</td><td>Point at tiles; activity stream fills</td>
<td class="say">"Real screen-shares over <b>LiveKit</b>; each frame → <b>Gemini Vision</b> → file/symbol/activity. A perception layer, not a chatbot."</td></tr>
<tr><td class="t">1:05</td><td>Two engineers edit the <b>same file</b>, unpushed</td>
<td class="say">"GitHub can't see this — nothing's pushed. We fuse live screen context with <b>local git truth</b>." → collision card appears.</td></tr>
<tr><td class="t">1:40</td><td><b>Accept</b> the card</td>
<td class="say">Real <b>GitHub sync-PR</b> created + outcome recorded. "One tap closes the loop."</td></tr>
<tr><td class="t">2:10</td><td><b>Trigger the same collision again</b></td>
<td class="say">Card now says <b>"Seen before."</b> and escalates to the <b>Gemini TTS voice</b>. "It recalled the prior event from <b>MongoDB Atlas vector search</b> and escalated — that's the learning."</td></tr>
<tr><td class="t">2:35</td><td>Cut to <b>GraphView</b></td>
<td class="say">"This graph is materialized <b>live from MongoDB right now</b> — and here's the <b>learned_from edge</b>: PodMan learned who owns this file. Not a mock."</td></tr>
<tr><td class="t">3:00</td><td>Stack + close</td>
<td class="say">"All on <b>DigitalOcean</b>, systemd-supervised; ambient score is <b>Gemini Lyria</b>. Awareness, collisions caught pre-push, memory that sharpens each session."</td></tr>
</tbody>
</table>
<h2>Sponsor coverage — say each ≥ once</h2>
<table>
<thead><tr><th>Prize</th><th>Spoken moment</th><th>Beat</th></tr></thead>
<tbody>
<tr><td><span class="pill p-blue">Gemini</span></td><td>Vision perception · TTS voice · Lyria score (Live conversation if time)</td><td class="muted">0:30 · 2:10 · 3:00</td></tr>
<tr><td><span class="pill p-violet">LiveKit</span></td><td>"real screen-shares over LiveKit"; TTS audio over LiveKit</td><td class="muted">0:30 · 2:10</td></tr>
<tr><td><span class="pill p-green">MongoDB</span></td><td>"Atlas vector-search recall" · "graph materialized live from MongoDB"</td><td class="muted">2:10 · 2:35</td></tr>
<tr><td><span class="pill p-amber">DigitalOcean</span></td><td>"all on DigitalOcean, systemd-supervised workers"</td><td class="muted">3:00</td></tr>
</tbody>
</table>
<h2 style="color:var(--red);border-color:var(--red)">⛔ Last-hour caveats — read before you run</h2>
<div class="warn">
<h3>1 · Suppression is DISABLED for the demo</h3>
<div class="muted">Every collision now surfaces (good — no accidental silencing). The old
<em>"it learned to stay QUIET / no card fires"</em> beat will <b>not</b> work. Use the
<b>escalate</b> beat instead: trigger the same collision again → <b>"Seen before." + voice +
learned_from edge</b>. Don't promise silence on stage.</div>
</div>
<div class="warn" style="margin-top:12px">
<h3>2 · Do NOT redeploy prod</h3>
<div class="muted">The live build has demo code not on <code>main</code> (e.g. <code>userPodContext</code>);
redeploying would remove it <em>and</em> surface stale "SUPPRESSED" beats. If the auto-deploy timer is on,
stop it: <code>systemctl stop podman-hermes-sync-deploy.timer</code>.</div>
</div>
<div class="warn" style="margin-top:12px">
<h3>3 · Never debug on stage</h3>
<div class="muted">Narrate, fall back to the backup recording, keep moving.</div>
</div>
<h2>If it breaks — live recovery</h2>
<table>
<thead><tr><th>Failure</th><th>Recovery</th></tr></thead>
<tbody>
<tr><td>Voice doesn't fire</td><td>Cut to the card, say the line aloud — cards are the default path. (Click "Enable audio" in-room first!)</td></tr>
<tr><td>Collision won't trigger</td><td>Use the backup recording for that beat; keep narrating.</td></tr>
<tr><td>Graph looks empty / odd</td><td>Reload once; if still off, narrate from the recording.</td></tr>
<tr><td>Agent silent</td><td>Suspect a duplicate <code>podman-hermes</code> process, not the UI — but don't fix on stage.</td></tr>
</tbody>
</table>
<h2>Pre-flight checklist</h2>
<div class="card">
<ol>
<li><code>curl https://podman.live/health</code><code>{"ok":true}</code></li>
<li>Deploy timer stopped; exactly one agent — <code>systemctl status podman-platform-agent</code></li>
<li>Browser on the pod; <b>click "Enable audio"</b> (TTS needs it)</li>
<li>Backup recording cued on a second device</li>
<li>Two screen-shares live (the two "engineers")</li>
</ol>
</div>
<footer>
<div class="spine">The spine: live awareness → collision caught pre-push → accept → "Seen before." + voice + learned_from edge → "this graph is live from MongoDB."</div>
<div style="margin-top:6px">Runs on the <code>demo</code> branch (ahead of <code>main</code>). Suppression intentionally off this run. · Generated for the final demo.</div>
</footer>
</div>
</body>
</html>
+1 -39
View File
@@ -8,10 +8,8 @@
<title>PodMan — the teammate that sees what git cant</title>
<meta
name="description"
content="PodMan is an ambient AI teammate that watches your pod's screens in realtime and catches merge collisions before anyone pushes. Top 3 at the AI Engineer World's Fair 2026 Hackathon — built by Karti Tripathi, Ramis Hasanli, Yahya Alhinai, and Shakthi Bachala."
content="PodMan is an ambient AI teammate that watches your pod's screens in realtime and catches merge collisions before anyone pushes."
/>
<meta name="author" content="Karti Tripathi, Ramis Hasanli, Yahya Alhinai, Shakthi Bachala" />
<link rel="canonical" href="https://podman.live/" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="PodMan" />
@@ -33,42 +31,6 @@
/>
<meta name="twitter:image" content="https://www.podman.live/og.png" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "PodMan",
"url": "https://podman.live",
"applicationCategory": "DeveloperApplication",
"operatingSystem": "Web",
"description": "An ambient AI teammate that watches your pod's screens in realtime and catches merge collisions, duplicated work, and missed handoffs before anyone pushes.",
"award": "Top 3 — AI Engineer World's Fair 2026 Hackathon (2nd overall of 95+ teams, LiveKit track winner, DigitalOcean Best Use)",
"author": [
{
"@type": "Person",
"name": "Karti Tripathi",
"url": "https://karti.ai",
"sameAs": "https://www.linkedin.com/in/kartitripathi/"
},
{
"@type": "Person",
"name": "Ramis Hasanli",
"sameAs": "https://www.linkedin.com/in/hasanliramis/"
},
{
"@type": "Person",
"name": "Yahya Alhinai",
"sameAs": "https://www.linkedin.com/in/yhinai/"
},
{
"@type": "Person",
"name": "Shakthi Bachala",
"sameAs": "https://www.linkedin.com/in/shakthi-bachala-09401510a/"
}
]
}
</script>
<script>
// Tear down any service worker we shipped earlier (vite-plugin-pwa).
// PodMan runs no PWA in production: we deploy continuously and SW-driven
-1
View File
@@ -30,7 +30,6 @@
"react-day-picker": "^10.0.1",
"react-dom": "^19.2.7",
"react-resizable-panels": "^4.11.2",
"react-router-dom": "^7.18.1",
"recharts": "3.8.0",
"shadcn": "^4.12.0",
"sonner": "^2.0.7",
+6 -63
View File
@@ -1,64 +1,7 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
<title id="title">PodMan Logo Mark</title>
<desc id="desc">A minimal rounded robot head with glowing eyes and a blue-violet antenna orb, optimized for white backgrounds.</desc>
<defs>
<filter id="softShadow" x="44" y="90" width="424" height="360" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="14" stdDeviation="20" flood-color="#94A3B8" flood-opacity="0.22"/>
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#C7D2FE" flood-opacity="0.20"/>
</filter>
<filter id="eyeGlow" x="138" y="234" width="236" height="96" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feColorMatrix in="blur" type="matrix" values="0 0 0 0 0.27 0 0 0 0 0.42 0 0 0 0 1 0 0 0 0.78 0" result="glow"/>
<feMerge>
<feMergeNode in="glow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="shell" x1="122" y1="144" x2="390" y2="420" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFFFFF"/>
<stop offset="0.56" stop-color="#F7FAFF"/>
<stop offset="1" stop-color="#EDF3FF"/>
</linearGradient>
<linearGradient id="face" x1="154" y1="220" x2="358" y2="334" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#050A1F"/>
<stop offset="0.48" stop-color="#081038"/>
<stop offset="1" stop-color="#10237A"/>
</linearGradient>
<linearGradient id="orb" x1="218" y1="38" x2="292" y2="113" gradientUnits="userSpaceOnUse">
<stop stop-color="#65D5FF"/>
<stop offset="0.46" stop-color="#6A65FF"/>
<stop offset="1" stop-color="#9A5CFF"/>
</linearGradient>
<radialGradient id="eye" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(256 280) rotate(90) scale(18)">
<stop offset="0" stop-color="#FFFFFF"/>
<stop offset="0.55" stop-color="#F6FBFF"/>
<stop offset="1" stop-color="#C8DDFF"/>
</radialGradient>
<linearGradient id="antenna" x1="256" y1="86" x2="256" y2="158" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F63FF"/>
<stop offset="1" stop-color="#1E3A8A"/>
</linearGradient>
</defs>
<!-- transparent background; tuned to read clearly on white surfaces -->
<!-- Antenna -->
<rect x="250" y="87" width="12" height="82" rx="6" fill="url(#antenna)"/>
<circle cx="256" cy="70" r="31" fill="url(#orb)"/>
<circle cx="244" cy="56" r="9" fill="white" opacity="0.38"/>
<!-- Head shell -->
<g filter="url(#softShadow)">
<rect x="96" y="146" width="320" height="260" rx="92" fill="url(#shell)" stroke="#DDE6F3" stroke-width="2"/>
<rect x="104" y="154" width="304" height="244" rx="84" stroke="white" stroke-opacity="0.84" stroke-width="12"/>
</g>
<!-- Face visor -->
<g filter="url(#eyeGlow)">
<rect x="146" y="226" width="220" height="104" rx="46" fill="url(#face)"/>
<circle cx="202" cy="278" r="18" fill="url(#eye)"/>
<circle cx="310" cy="278" r="18" fill="url(#eye)"/>
<circle cx="202" cy="278" r="8" fill="white" opacity="0.96"/>
<circle cx="310" cy="278" r="8" fill="white" opacity="0.96"/>
</g>
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<rect width="32" height="32" rx="7" fill="#0b0f17" />
<circle cx="25" cy="6.6" r="2.4" fill="#34d399" />
<text x="14.5" y="24.5" text-anchor="middle"
font-family="ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif"
font-weight="700" font-size="24" fill="#34d399">P</text>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 416 B

-64
View File
@@ -1,64 +0,0 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-labelledby="title desc">
<title id="title">PodMan Logo Mark</title>
<desc id="desc">A minimal rounded robot head with glowing eyes and a blue-violet antenna orb, optimized for white backgrounds.</desc>
<defs>
<filter id="softShadow" x="44" y="90" width="424" height="360" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feDropShadow dx="0" dy="14" stdDeviation="20" flood-color="#94A3B8" flood-opacity="0.22"/>
<feDropShadow dx="0" dy="4" stdDeviation="8" flood-color="#C7D2FE" flood-opacity="0.20"/>
</filter>
<filter id="eyeGlow" x="138" y="234" width="236" height="96" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation="6" result="blur"/>
<feColorMatrix in="blur" type="matrix" values="0 0 0 0 0.27 0 0 0 0 0.42 0 0 0 0 1 0 0 0 0.78 0" result="glow"/>
<feMerge>
<feMergeNode in="glow"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<linearGradient id="shell" x1="122" y1="144" x2="390" y2="420" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#FFFFFF"/>
<stop offset="0.56" stop-color="#F7FAFF"/>
<stop offset="1" stop-color="#EDF3FF"/>
</linearGradient>
<linearGradient id="face" x1="154" y1="220" x2="358" y2="334" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#050A1F"/>
<stop offset="0.48" stop-color="#081038"/>
<stop offset="1" stop-color="#10237A"/>
</linearGradient>
<linearGradient id="orb" x1="218" y1="38" x2="292" y2="113" gradientUnits="userSpaceOnUse">
<stop stop-color="#65D5FF"/>
<stop offset="0.46" stop-color="#6A65FF"/>
<stop offset="1" stop-color="#9A5CFF"/>
</linearGradient>
<radialGradient id="eye" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(256 280) rotate(90) scale(18)">
<stop offset="0" stop-color="#FFFFFF"/>
<stop offset="0.55" stop-color="#F6FBFF"/>
<stop offset="1" stop-color="#C8DDFF"/>
</radialGradient>
<linearGradient id="antenna" x1="256" y1="86" x2="256" y2="158" gradientUnits="userSpaceOnUse">
<stop stop-color="#4F63FF"/>
<stop offset="1" stop-color="#1E3A8A"/>
</linearGradient>
</defs>
<!-- transparent background; tuned to read clearly on white surfaces -->
<!-- Antenna -->
<rect x="250" y="87" width="12" height="82" rx="6" fill="url(#antenna)"/>
<circle cx="256" cy="70" r="31" fill="url(#orb)"/>
<circle cx="244" cy="56" r="9" fill="white" opacity="0.38"/>
<!-- Head shell -->
<g filter="url(#softShadow)">
<rect x="96" y="146" width="320" height="260" rx="92" fill="url(#shell)" stroke="#DDE6F3" stroke-width="2"/>
<rect x="104" y="154" width="304" height="244" rx="84" stroke="white" stroke-opacity="0.84" stroke-width="12"/>
</g>
<!-- Face visor -->
<g filter="url(#eyeGlow)">
<rect x="146" y="226" width="220" height="104" rx="46" fill="url(#face)"/>
<circle cx="202" cy="278" r="18" fill="url(#eye)"/>
<circle cx="310" cy="278" r="18" fill="url(#eye)"/>
<circle cx="202" cy="278" r="8" fill="white" opacity="0.96"/>
<circle cx="310" cy="278" r="8" fill="white" opacity="0.96"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 3.3 KiB

+13 -15
View File
@@ -9,7 +9,11 @@ import {
useUser,
} from '@clerk/react';
import type { Room } from 'livekit-client';
import { AlertCircleIcon, RefreshCwIcon, SparklesIcon } from 'lucide-react';
import {
AlertCircleIcon,
RefreshCwIcon,
SparklesIcon,
} from 'lucide-react';
import type { Pod, PodInput } from '@podman/shared';
import { joinPod } from './lib/pod.js';
import * as api from './lib/api.js';
@@ -338,12 +342,9 @@ export default function App() {
<header className="sticky top-0 z-10 -mx-4 border-b bg-background/86 px-4 pb-4 pt-2 backdrop-blur-xl sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div className="flex min-w-0 items-center gap-3">
<img
src="/podman-logo.svg"
alt=""
className="size-11 shrink-0 rounded-lg"
aria-hidden="true"
/>
<div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm">
PM
</div>
<div className="min-w-0">
<h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">PodMan</h1>
</div>
@@ -467,12 +468,9 @@ function AuthGate() {
<div className="mx-auto flex min-h-screen w-full max-w-[1120px] flex-col px-4 py-4 sm:px-6 lg:px-8">
<header className="flex items-center justify-between border-b pb-4 pt-2">
<div className="flex min-w-0 items-center gap-3">
<img
src="/podman-logo.svg"
alt=""
className="size-11 shrink-0 rounded-lg"
aria-hidden="true"
/>
<div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm">
PM
</div>
<h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">PodMan</h1>
</div>
<SignInButton mode="modal">
@@ -487,8 +485,8 @@ function AuthGate() {
Create your account to enter PodMan
</h2>
<p className="mt-3 text-base text-muted-foreground">
PodMan saves your context across pods so agents can learn from your work in every room
you join.
PodMan saves your context across pods so agents can learn from your work in every
room you join.
</p>
</section>
-307
View File
@@ -1,307 +0,0 @@
import { useCallback } from 'react';
import {
TrophyIcon,
ChevronDownIcon,
EyeIcon,
GitMergeIcon,
UsersIcon,
BellIcon,
RadioIcon,
DatabaseIcon,
SparklesIcon,
PlayIcon,
MicIcon,
} from 'lucide-react';
/**
* Public marketing landing page for podman.live.
* Rendered at "/" without ClerkProvider — needs no secrets.
* The app (private beta) lives at "/app".
*/
const TEAM = [
{ name: 'Karti Tripathi', url: 'https://www.linkedin.com/in/kartitripathi/' },
{ name: 'Ramis Hasanli', url: 'https://www.linkedin.com/in/hasanliramis/' },
{ name: 'Yahya Alhinai', url: 'https://www.linkedin.com/in/yhinai/' },
{ name: 'Shakthi Bachala', url: 'https://www.linkedin.com/in/shakthi-bachala-09401510a/' },
];
export function Landing() {
const scrollToStory = useCallback(() => {
document.getElementById('story')?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, []);
return (
<div className="min-h-screen scroll-smooth bg-[#0b0f17] text-white/90 antialiased">
{/* ambient glows */}
<div className="pointer-events-none fixed inset-0 overflow-hidden">
<div className="absolute -top-40 left-1/2 h-[38rem] w-[38rem] -translate-x-1/2 rounded-full bg-indigo-600/20 blur-[120px]" />
<div className="absolute top-1/3 -right-32 h-[26rem] w-[26rem] rounded-full bg-cyan-500/10 blur-[110px]" />
</div>
{/* top nav */}
<header className="relative z-20 mx-auto flex max-w-6xl items-center justify-between px-6 py-5">
<a href="#top" className="flex items-center gap-2.5">
<img src="/podman-logo.svg" alt="PodMan" className="h-7 w-7" />
<span className="text-[15px] font-semibold tracking-tight text-white">PodMan</span>
</a>
<span className="inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-4 py-1.5 text-sm font-medium text-white/70">
<LiveDot />
Beta soon
</span>
</header>
{/* hero */}
<section id="top" className="relative z-10 mx-auto flex max-w-3xl flex-col items-center px-6 pt-16 pb-24 text-center sm:pt-24">
<button
onClick={scrollToStory}
className="group mb-8 inline-flex items-center gap-2 rounded-full border border-amber-300/30 bg-gradient-to-r from-amber-400/15 to-amber-200/5 px-4 py-1.5 text-sm font-medium text-amber-200 transition-colors hover:border-amber-300/50"
>
<TrophyIcon className="size-4" />
Top 3 AI Engineer World&apos;s Fair Hackathon 2026
<ChevronDownIcon className="size-4 transition-transform group-hover:translate-y-0.5" />
</button>
<h1 className="text-5xl font-semibold leading-[1.05] tracking-tight text-white sm:text-7xl">
PodMan
</h1>
<p className="mt-5 bg-gradient-to-r from-indigo-300 via-sky-300 to-cyan-200 bg-clip-text text-2xl font-medium text-transparent sm:text-3xl">
The teammate that sees what git can&apos;t.
</p>
<p className="mx-auto mt-6 max-w-xl text-base leading-relaxed text-white/60 sm:text-lg">
An ambient AI teammate that watches your pod&apos;s screens in realtime catching merge
collisions, duplicated work, and missed handoffs before anyone pushes.
</p>
{/* award chips */}
<div className="mt-9 flex flex-wrap items-center justify-center gap-2.5">
{[
{ label: '2nd Overall · 95+ teams', accent: 'text-amber-200 border-amber-300/25 bg-amber-400/10' },
{ label: 'LiveKit Track — Winner', accent: 'text-sky-200 border-sky-300/25 bg-sky-400/10' },
{ label: 'DigitalOcean — Best Use', accent: 'text-cyan-200 border-cyan-300/25 bg-cyan-400/10' },
].map((c) => (
<span
key={c.label}
className={`rounded-full border px-3.5 py-1.5 text-[13px] font-medium ${c.accent}`}
>
{c.label}
</span>
))}
</div>
{/* CTAs */}
<div className="mt-10 flex flex-col items-center gap-3 sm:flex-row">
<button
onClick={scrollToStory}
className="inline-flex h-11 items-center justify-center gap-2 rounded-full bg-gradient-to-r from-indigo-500 to-cyan-400 px-7 text-sm font-semibold text-[#0b0f17] shadow-lg shadow-indigo-500/20 transition-transform hover:scale-[1.02]"
>
Read the story <ChevronDownIcon className="size-4" />
</button>
<BetaPill />
</div>
<button
onClick={scrollToStory}
aria-label="Scroll to read"
className="mt-20 animate-bounce text-white/40 transition-colors hover:text-white/70"
>
<ChevronDownIcon className="size-7" />
</button>
</section>
{/* story */}
<Section id="story" eyebrow="How it happened" title="Built on stage, in about 24 hours.">
<div className="space-y-5 text-[15px] leading-relaxed text-white/70">
<p>
PodMan was built at the{' '}
<span className="text-white">AI Engineer World&apos;s Fair 2026 Hackathon</span> (June
2728) against 95+ teams. It took{' '}
<span className="text-amber-200">2nd place overall</span>, and won both the{' '}
<span className="text-sky-200">LiveKit</span> track and{' '}
<span className="text-cyan-200">DigitalOcean&apos;s Best Use</span> award.
</p>
<p>
We shipped the live demo in a single sprint a LiveKit room, Gemini watching the
screen-share, and a voice that speaks up the moment two engineers drift toward the same
code. There wasn&apos;t time on the clock to give it a proper home.
</p>
<p className="text-white/90">
So this is that home and we&apos;re actively building PodMan out from the hackathon
prototype into something a team can actually run every day.
</p>
</div>
</Section>
{/* what it does */}
<Section id="what" eyebrow="What it does" title="A teammate in the room, not another dashboard.">
<div className="grid gap-4 sm:grid-cols-2">
<Feature
icon={<EyeIcon className="size-5" />}
title="Ambient awareness"
body="Joins your pod's LiveKit room and samples every screen-share in realtime — no bots to invoke, no commands to remember."
/>
<Feature
icon={<GitMergeIcon className="size-5" />}
title="Merge-collision radar"
body="Recognizes when two people are converging on the same files or feature and warns them before the conflict ever reaches a push."
/>
<Feature
icon={<UsersIcon className="size-5" />}
title="Duplicated-work detection"
body="Notices when effort overlaps across the team and surfaces it early, so two engineers don't quietly build the same thing."
/>
<Feature
icon={<BellIcon className="size-5" />}
title="Handoff nudges"
body="Speaks up about missed handoffs and stalled threads — and learns which nudges actually helped, so it stays useful, not noisy."
/>
</div>
</Section>
{/* how it works */}
<Section id="how" eyebrow="Under the hood" title="Realtime vision, memory, and a voice.">
<div className="grid gap-4 sm:grid-cols-3">
<Stack icon={<RadioIcon className="size-5" />} name="LiveKit" note="Realtime transport for the shared room and voice delivery." />
<Stack icon={<SparklesIcon className="size-5" />} name="Gemini" note="Vision + event detection + the live voice that nudges the team." />
<Stack icon={<DatabaseIcon className="size-5" />} name="MongoDB" note="Engineer state, ownership map, events, and which nudges landed." />
</div>
<div className="mt-6 flex flex-wrap gap-2 text-[13px] text-white/50">
{['LiveKit', 'Gemini Live', 'MongoDB Atlas', 'Voyage embeddings', 'Modular MAX', 'Hermes ops'].map((t) => (
<span key={t} className="rounded-md border border-white/10 bg-white/[0.03] px-2.5 py-1">
{t}
</span>
))}
</div>
</Section>
{/* demo */}
<Section id="demo" eyebrow="See it" title="The 60-second demo.">
<div className="overflow-hidden rounded-2xl border border-white/10 bg-black/40 shadow-2xl">
<div className="relative w-full" style={{ paddingBottom: '56.25%' }}>
<iframe
className="absolute inset-0 h-full w-full"
src="https://www.youtube-nocookie.com/embed/bWJIsIWTgr0"
title="PodMan demo"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
/>
</div>
</div>
<div className="mt-4 flex items-center gap-2 text-sm text-white/40">
<PlayIcon className="size-4" /> Watch the pod get its warning before the collision.
</div>
</Section>
{/* built by / team */}
<Section id="team" eyebrow="Built by" title="The team behind PodMan.">
<div className="flex flex-wrap gap-3">
{TEAM.map((p) => (
<a
key={p.name}
href={p.url}
target="_blank"
rel="noopener noreferrer"
className="rounded-full border border-white/10 bg-white/[0.03] px-4 py-2 text-[15px] font-medium text-white/80 transition-colors hover:border-cyan-300/40 hover:text-white"
>
{p.name}
</a>
))}
</div>
</Section>
{/* final CTA — beta coming soon */}
<section className="relative z-10 mx-auto max-w-3xl px-6 py-24 text-center">
<div className="rounded-3xl border border-white/10 bg-gradient-to-b from-white/[0.06] to-transparent px-8 py-14">
<MicIcon className="mx-auto size-8 text-cyan-300" />
<h2 className="mt-5 text-3xl font-semibold tracking-tight text-white sm:text-4xl">
The private beta is coming soon.
</h2>
<p className="mx-auto mt-4 max-w-md text-[15px] leading-relaxed text-white/60">
We&apos;re building PodMan out from the hackathon prototype into something a team runs
every day. The private beta opens soon.
</p>
<div className="mt-8 flex justify-center">
<BetaPill className="px-6 py-3 text-[15px]" />
</div>
</div>
</section>
{/* footer */}
<footer className="relative z-10 border-t border-white/10">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-3 px-6 py-8 text-sm text-white/40 sm:flex-row">
<div className="flex items-center gap-2">
<img src="/podman-logo.svg" alt="" className="h-5 w-5 opacity-70" />
<span>PodMan · podman.live</span>
</div>
<span>Built at AI Engineer World&apos;s Fair 2026 · 2nd Overall</span>
</div>
</footer>
</div>
);
}
function LiveDot() {
return (
<span className="relative flex size-1.5">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-cyan-400/70" />
<span className="relative inline-flex size-1.5 rounded-full bg-cyan-400" />
</span>
);
}
function BetaPill({ className = '' }: { className?: string }) {
return (
<span
className={`inline-flex items-center gap-2 rounded-full border border-white/15 bg-white/5 px-5 py-2.5 text-sm font-medium text-white/70 ${className}`}
>
<LiveDot />
Private beta coming soon
</span>
);
}
function Section({
id,
eyebrow,
title,
children,
}: {
id: string;
eyebrow: string;
title: string;
children: React.ReactNode;
}) {
return (
<section id={id} className="relative z-10 border-t border-white/[0.06] py-20">
<div className="mx-auto max-w-3xl px-6">
<p className="text-sm font-medium tracking-wide text-cyan-300/80 uppercase">{eyebrow}</p>
<h2 className="mt-3 text-3xl font-semibold tracking-tight text-white sm:text-4xl">{title}</h2>
<div className="mt-8">{children}</div>
</div>
</section>
);
}
function Feature({ icon, title, body }: { icon: React.ReactNode; title: string; body: string }) {
return (
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6 transition-colors hover:border-white/20">
<div className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-indigo-500/20 to-cyan-400/20 text-cyan-200">
{icon}
</div>
<h3 className="mt-4 text-lg font-semibold text-white">{title}</h3>
<p className="mt-2 text-[14px] leading-relaxed text-white/60">{body}</p>
</div>
);
}
function Stack({ icon, name, note }: { icon: React.ReactNode; name: string; note: string }) {
return (
<div className="rounded-2xl border border-white/10 bg-white/[0.03] p-6">
<div className="flex items-center gap-2.5 text-white">
<span className="text-cyan-200">{icon}</span>
<span className="text-base font-semibold">{name}</span>
</div>
<p className="mt-3 text-[14px] leading-relaxed text-white/60">{note}</p>
</div>
);
}
+7 -36
View File
@@ -1,53 +1,24 @@
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { ClerkProvider } from '@clerk/react';
import { shadcn } from '@clerk/ui/themes';
import App from './App.js';
import { Landing } from './Landing.js';
import './index.css';
import '@clerk/ui/themes/shadcn.css';
import { TooltipProvider } from '@/components/ui/tooltip';
const clerkPublishableKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY;
const hasClerk = typeof clerkPublishableKey === 'string' && clerkPublishableKey.startsWith('pk_');
function AppRoute() {
if (!hasClerk) {
// Secrets not provisioned yet — show a graceful placeholder instead of crashing.
return (
<div className="flex min-h-screen flex-col items-center justify-center gap-3 bg-[#0b0f17] px-6 text-center text-white/80">
<img src="/podman-logo.svg" alt="PodMan" className="h-10 w-10" />
<h1 className="text-xl font-semibold text-white">PodMan is deploying</h1>
<p className="max-w-sm text-sm text-white/50">
The live app is being provisioned. Check back shortly.
</p>
<a href="/" className="mt-2 text-sm text-cyan-300 hover:underline">
Back to home
</a>
</div>
);
}
return (
<ClerkProvider
publishableKey={clerkPublishableKey}
appearance={{ theme: shadcn }}
afterSignOutUrl="/"
>
<TooltipProvider>
<App />
</TooltipProvider>
</ClerkProvider>
);
if (!clerkPublishableKey) {
throw new Error('Missing VITE_CLERK_PUBLISHABLE_KEY');
}
createRoot(document.getElementById('root')!).render(
<StrictMode>
<BrowserRouter>
<Routes>
<Route path="/" element={<Landing />} />
<Route path="/app/*" element={<AppRoute />} />
</Routes>
</BrowserRouter>
<ClerkProvider publishableKey={clerkPublishableKey} appearance={{ theme: shadcn }}>
<TooltipProvider>
<App />
</TooltipProvider>
</ClerkProvider>
</StrictMode>,
);
-45
View File
@@ -154,9 +154,6 @@ importers:
react-resizable-panels:
specifier: ^4.11.2
version: 4.11.2(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react-router-dom:
specifier: ^7.18.1
version: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
recharts:
specifier: 3.8.0
version: 3.8.0(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react-is@19.2.7)(react@19.2.7)(redux@5.0.1)
@@ -3841,10 +3838,6 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
copy-to-clipboard@3.3.3:
resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==}
@@ -5791,23 +5784,6 @@ packages:
react: ^18.0.0 || ^19.0.0
react-dom: ^18.0.0 || ^19.0.0
react-router-dom@7.18.1:
resolution: {integrity: sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
react-router@7.18.1:
resolution: {integrity: sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==}
engines: {node: '>=20.0.0'}
peerDependencies:
react: '>=18'
react-dom: '>=18'
peerDependenciesMeta:
react-dom:
optional: true
react-style-singleton@2.2.3:
resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==}
engines: {node: '>=10'}
@@ -6011,9 +5987,6 @@ packages:
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
set-cookie-parser@2.7.2:
resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==}
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -10697,8 +10670,6 @@ snapshots:
cookie@0.7.2: {}
cookie@1.1.1: {}
copy-to-clipboard@3.3.3:
dependencies:
toggle-selection: 1.0.6
@@ -12835,20 +12806,6 @@ snapshots:
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
react-router-dom@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
react: 19.2.7
react-dom: 19.2.7(react@19.2.7)
react-router: 7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7)
react-router@7.18.1(react-dom@19.2.7(react@19.2.7))(react@19.2.7):
dependencies:
cookie: 1.1.1
react: 19.2.7
set-cookie-parser: 2.7.2
optionalDependencies:
react-dom: 19.2.7(react@19.2.7)
react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.7):
dependencies:
get-nonce: 1.0.1
@@ -13147,8 +13104,6 @@ snapshots:
set-blocking@2.0.0: {}
set-cookie-parser@2.7.2: {}
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4