Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 82e5378658 | |||
| 26c851e2bb | |||
| 28f3236d76 | |||
| 70b41104ad | |||
| a97142cf09 | |||
| bc50406587 | |||
| 88abd8efb7 |
@@ -7,9 +7,55 @@ 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/) ·
|
||||
[vLLM](https://vllm.ai/) · [DigitalOcean](https://www.digitalocean.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.
|
||||
|
||||
## Read This First
|
||||
|
||||
@@ -19,10 +65,68 @@ down.
|
||||
| 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-vllm --model gemma-4-31B-it` |
|
||||
| Test Hermes | `hermes -z 'Reply with exactly: working' --provider gemma4-31b-max --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
|
||||
@@ -33,10 +137,10 @@ down.
|
||||
| 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 Gemma/vLLM endpoint |
|
||||
| Reasoning LLM | `https://llm.alhinai.dev/v1` | OpenAI-compatible Modular/MAX 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-vllm` | Custom provider used by Hermes locally |
|
||||
| Hermes config | `gemma4-31b-max` | Custom provider used by Hermes locally |
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
@@ -66,8 +170,8 @@ flowchart TB
|
||||
|
||||
subgraph Reasoning["External Reasoning Endpoint"]
|
||||
Tunnel["Cloudflare Tunnel<br/>llm.alhinai.dev"]
|
||||
VLLM["vLLM OpenAI Server<br/>gemma-4-31B-it<br/>262144 context"]
|
||||
Hermes["Hermes Agent<br/>provider: gemma4-31b-vllm"]
|
||||
MaxServe["MAX OpenAI Server<br/>gemma-4-31B-it<br/>262144 context"]
|
||||
Hermes["Hermes Agent<br/>provider: gemma4-31b-max"]
|
||||
end
|
||||
|
||||
Browser --> Caddy --> API
|
||||
@@ -84,7 +188,8 @@ flowchart TB
|
||||
Voice --> GeminiVoice
|
||||
Vision --> GeminiEmbed
|
||||
Voice --> GeminiEmbed
|
||||
Ops --> Hermes --> Tunnel --> VLLM
|
||||
Ops --> Hermes --> Tunnel --> MaxServe
|
||||
Tunnel -. "public route<br/>llm.alhinai.dev/v1" .-> MaxServe
|
||||
API --> Hermes
|
||||
Lyria --> Voice
|
||||
|
||||
@@ -95,7 +200,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,VLLM,Hermes ai;
|
||||
class GeminiVision,GeminiVoice,GeminiEmbed,Lyria,Tunnel,MaxServe,Hermes ai;
|
||||
```
|
||||
|
||||
## What It Does
|
||||
@@ -138,7 +243,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/>Hermes + vLLM + tools"]
|
||||
HermesRoute --> Gemma["gemma-4-31B-it<br/>served by Modular MAX"]
|
||||
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"]
|
||||
@@ -151,7 +256,7 @@ flowchart LR
|
||||
class Gemini20,GeminiLive,GeminiTTS,Gemma,Voyage,GeminiEmbed,Lyria model;
|
||||
```
|
||||
|
||||
### Gemma 4 31B via vLLM
|
||||
### Gemma 4 31B via Modular MAX
|
||||
|
||||
Hermes uses the external OpenAI-compatible endpoint:
|
||||
|
||||
@@ -162,17 +267,16 @@ Model: gemma-4-31B-it
|
||||
Context: 262144 tokens
|
||||
```
|
||||
|
||||
The vLLM server is configured for Hermes-style tool use:
|
||||
The Modular/MAX server is configured for long-context Gemma 4 serving:
|
||||
|
||||
```text
|
||||
--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
|
||||
--max-length 262144
|
||||
--device-memory-utilization 0.85
|
||||
--kv-cache-format float8_e4m3fn
|
||||
--enable-prefix-caching
|
||||
--enable-chunked-prefill
|
||||
--max-batch-size 1
|
||||
--max-batch-input-tokens 16384
|
||||
```
|
||||
|
||||
Hermes should point at that endpoint with this provider shape:
|
||||
@@ -180,11 +284,11 @@ Hermes should point at that endpoint with this provider shape:
|
||||
```yaml
|
||||
model:
|
||||
default: gemma-4-31B-it
|
||||
provider: gemma4-31b-vllm
|
||||
provider: gemma4-31b-max
|
||||
|
||||
providers:
|
||||
gemma4-31b-vllm:
|
||||
name: Gemma 4 31B vLLM (256K)
|
||||
gemma4-31b-max:
|
||||
name: Gemma 4 31B Modular MAX (256K)
|
||||
api: https://llm.alhinai.dev/v1
|
||||
api_key: not-needed
|
||||
transport: chat_completions
|
||||
@@ -199,8 +303,25 @@ agent:
|
||||
```
|
||||
|
||||
Why this matters: Hermes sends OpenAI tool schemas and `tool_choice: "auto"`.
|
||||
Without `--enable-auto-tool-choice` and `--tool-call-parser gemma4`, vLLM returns
|
||||
HTTP 400 before Hermes can initialize an agent.
|
||||
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 |
|
||||
|
||||
### Gemini Surfaces
|
||||
|
||||
@@ -251,7 +372,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 vLLM | Tool-using autonomous assistant and ops layer |
|
||||
| Reasoning agent | Hermes + Gemma Modular/MAX | 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 |
|
||||
|
||||
@@ -433,7 +554,7 @@ pnpm hermes:sync-deploy
|
||||
pnpm deploy:doctor:strict
|
||||
```
|
||||
|
||||
Gemma/vLLM endpoint checks:
|
||||
Gemma Modular/MAX endpoint checks:
|
||||
|
||||
```bash
|
||||
curl https://llm.alhinai.dev/v1/models \
|
||||
@@ -481,7 +602,7 @@ POD_ROOM=demo-pod
|
||||
Gemma/Hermes provider values live in Hermes config, not PodMan `.env`:
|
||||
|
||||
```text
|
||||
provider: gemma4-31b-vllm
|
||||
provider: gemma4-31b-max
|
||||
model: gemma-4-31B-it
|
||||
base_url: https://llm.alhinai.dev/v1
|
||||
api_key: not-needed
|
||||
@@ -514,7 +635,7 @@ For Hermes/Gemma:
|
||||
|
||||
```bash
|
||||
hermes -z 'Reply with exactly: working' \
|
||||
--provider gemma4-31b-vllm \
|
||||
--provider gemma4-31b-max \
|
||||
--model gemma-4-31B-it
|
||||
```
|
||||
|
||||
@@ -528,15 +649,15 @@ 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 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 |
|
||||
| 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 |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -1,56 +0,0 @@
|
||||
# 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.
|
||||
+129
-54
@@ -1,72 +1,133 @@
|
||||
# PodMan — 4-Minute Demo Script
|
||||
# PodMan — Demo Scripts
|
||||
|
||||
**Theme:** Continual Learning. **Hard limit:** 4:00. Practice to land at 3:45.
|
||||
**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.
|
||||
|
||||
**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 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 hook to land:** a "quick five-minute question" actually costs ~25 minutes of
|
||||
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.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## 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:00–0: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:12–0: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:35–0: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:52–1: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:00–0:30 — The problem + hook
|
||||
|
||||
> "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."
|
||||
> "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."
|
||||
|
||||
_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:30–1:05 — Real-time team awareness (LiveKit + Gemini Vision)
|
||||
|
||||
- Point at the two live screen tiles. "These are real screen shares over
|
||||
**LiveKit**. Our agent subscribes to the tracks and samples frames."
|
||||
**LiveKit**. Our agent subscribes to the tracks and samples frames."
|
||||
- "Each frame goes to **Gemini Vision**, which returns structured context — file,
|
||||
symbol, activity — not a chatbot, a perception layer."
|
||||
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
|
||||
ask."
|
||||
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:05–1:40 — The catch (detection + first intervention)
|
||||
|
||||
- Have alice and bob both edit the **same file** with unpushed changes.
|
||||
- "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."_
|
||||
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."*
|
||||
- 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."
|
||||
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:40–2: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
|
||||
(docs.livekit.io) while alice edits livekit.py — sync up before duplicating
|
||||
effort."_
|
||||
- A collaboration nudge appears: *"🤝 bob is researching LiveKit agents
|
||||
(docs.livekit.io) while alice edits livekit.py — sync up before duplicating
|
||||
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."
|
||||
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:10–2:50 — Continual learning (the theme — the money shot)
|
||||
@@ -74,52 +135,58 @@ _Built-by-us callout:_ `vision/gemini.ts`, `collision/research.ts`,
|
||||
This is the differentiator. Two beats, both from pre-seeded memory:
|
||||
|
||||
1. **It learned to stay quiet.** Trigger a pattern that was dismissed as a false
|
||||
alarm earlier. "Last session a teammate marked this kind of alert as not a
|
||||
alarm earlier. "Last session a teammate marked this kind of alert as not a
|
||||
real conflict. Watch — PodMan stays silent. No nagging." (No card fires.)
|
||||
2. **It learned to escalate.** Trigger the real-conflict pattern that was
|
||||
accepted before. The card now says **"Seen before."** and goes straight to
|
||||
accepted before. The card now says **"Seen before."** and goes straight to
|
||||
the spoken urgent cue.
|
||||
|
||||
- "The only input was one accept/dismiss tap. No retraining, no labeling. This is
|
||||
**MongoDB Atlas vector search** recalling similar past events plus a policy
|
||||
that adapts on the recalled outcome."
|
||||
**MongoDB Atlas vector search** recalling similar past events plus a policy
|
||||
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:50–3: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?"_
|
||||
you ask PodMan."
|
||||
- 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.
|
||||
collisions — not guesses.
|
||||
- "This is the **Gemini Live API**, streaming speech-to-speech over LiveKit, with
|
||||
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."
|
||||
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:30–3: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.
|
||||
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 teammate. That's
|
||||
continual learning, shipped."
|
||||
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
|
||||
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
|
||||
continual learning, shipped."
|
||||
|
||||
|
||||
|
||||
### 3:50–4: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 |
|
||||
@@ -127,10 +194,14 @@ _Built-by-us callout:_ `agents/podman-live-conversation/agent.py`.
|
||||
| **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. |
|
||||
@@ -138,12 +209,16 @@ _Built-by-us callout:_ `agents/podman-live-conversation/agent.py`.
|
||||
| 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 |
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
<!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>
|
||||
+39
-1
@@ -8,8 +8,10 @@
|
||||
<title>PodMan — the teammate that sees what git can’t</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."
|
||||
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."
|
||||
/>
|
||||
<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" />
|
||||
@@ -31,6 +33,42 @@
|
||||
/>
|
||||
<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
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,64 @@
|
||||
<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 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: 416 B After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,64 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
+15
-13
@@ -9,11 +9,7 @@ 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';
|
||||
@@ -342,9 +338,12 @@ 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">
|
||||
<div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm">
|
||||
PM
|
||||
</div>
|
||||
<img
|
||||
src="/podman-logo.svg"
|
||||
alt=""
|
||||
className="size-11 shrink-0 rounded-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">PodMan</h1>
|
||||
</div>
|
||||
@@ -468,9 +467,12 @@ 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">
|
||||
<div className="grid size-10 place-items-center rounded-lg bg-primary text-sm font-semibold text-primary-foreground shadow-sm">
|
||||
PM
|
||||
</div>
|
||||
<img
|
||||
src="/podman-logo.svg"
|
||||
alt=""
|
||||
className="size-11 shrink-0 rounded-lg"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h1 className="text-[1.95rem] font-semibold leading-none tracking-tight">PodMan</h1>
|
||||
</div>
|
||||
<SignInButton mode="modal">
|
||||
@@ -485,8 +487,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>
|
||||
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
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'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'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'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's Fair 2026 Hackathon</span> (June
|
||||
27–28) 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'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't time on the clock to give it a proper home.
|
||||
</p>
|
||||
<p className="text-white/90">
|
||||
So this is that home — and we'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'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'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>
|
||||
);
|
||||
}
|
||||
+36
-7
@@ -1,24 +1,53 @@
|
||||
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_');
|
||||
|
||||
if (!clerkPublishableKey) {
|
||||
throw new Error('Missing VITE_CLERK_PUBLISHABLE_KEY');
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<ClerkProvider publishableKey={clerkPublishableKey} appearance={{ theme: shadcn }}>
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<Landing />} />
|
||||
<Route path="/app/*" element={<AppRoute />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
Generated
+45
@@ -154,6 +154,9 @@ 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)
|
||||
@@ -3838,6 +3841,10 @@ 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==}
|
||||
|
||||
@@ -5784,6 +5791,23 @@ 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'}
|
||||
@@ -5987,6 +6011,9 @@ 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'}
|
||||
@@ -10670,6 +10697,8 @@ snapshots:
|
||||
|
||||
cookie@0.7.2: {}
|
||||
|
||||
cookie@1.1.1: {}
|
||||
|
||||
copy-to-clipboard@3.3.3:
|
||||
dependencies:
|
||||
toggle-selection: 1.0.6
|
||||
@@ -12806,6 +12835,20 @@ 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
|
||||
@@ -13104,6 +13147,8 @@ 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
|
||||
|
||||
Reference in New Issue
Block a user