Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,17 @@ vibepod-agents image entrypoint), the per-agent defaults in
The local-image fallback (`VP_IMAGE_<AGENT>` override + pull-failure fallback
in `run.py`/`task.py`) is how an unreleased image is exercised locally.

## State-reporting integrations (herdr, dash)

`core/herdr.py` and `core/dash.py` share the same shape: a config gate, a
data-driven map of vendored files to inject into the agent config dir, and
per-agent registration (claude `settings.json`, codex `.codex/hooks.json`).
Both copy through `core/hooksync.py`, and both must soft-fail — a broken
integration never blocks a run. The dash client scripts are vendored from the
vibepod-dash repo; see `src/vibepod/resources/dash/README.md` before editing
them. Codex registration is shared through `core/codex_hooks.py`, which lets
Dash and herdr coexist and removes only VibePod's legacy `notify` entries.

## Tests

Runner is `pytest` (`python -m pytest`); CI also validates default images with
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ tracking, and an analytics dashboard to monitor and compare agents side-by-side.
- 🧱 **Project overlays** — commit a `FROM`-less Dockerfile fragment in `.vibepod/overlay/` and VibePod auto-builds a cached, content-addressed image layer on top of the agent's base image — one clearly named image per project and agent ([docs](https://vibepod.dev/docs/overlays/))
- 📊 **Local analytics dashboard** — track usage and HTTP traffic per agent, plus token metrics
- 🐑 **Herdr aware** — `vp run` inside a [herdr](https://herdr.dev/) pane reports agent state automatically
- 📱 **Web dashboard** — point `VPDASH_URL` at a [VibePod Dash](https://github.com/VibePod/vibepod-dash) board and watch every running agent's state from your phone ([docs](https://vibepod.dev/docs/dash/))
- ⚖️ **Agent comparison** — benchmark multiple agents against each other in the dashboard
- 🔒 **Privacy-first** — all metrics collected and stored locally, never sent to the cloud
- 📦 **Simple install** — via pip, Homebrew, or conda-forge
Expand Down
12 changes: 12 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ logging:
db_path: ~/.config/vibepod/logs.db
ui_port: 8001 # Port for the Datasette UI

# Report agent state to a VibePod Dash board (see the Dash integration page)
dash:
enabled: true
url: "" # e.g. http://localhost:8765; empty disables reporting
token: "" # ingest token of the dash server
container_url: "" # Override the URL agents use; defaults to `url` with
# localhost rewritten to host.docker.internal
integrations: {} # Extra hook files per agent, {agent: [{source, dest}]}

proxy:
enabled: true
image: vibepod/proxy:latest
Expand Down Expand Up @@ -202,6 +211,9 @@ These variables override the corresponding config keys without editing any file:
| `VP_LLM_BASE_URL` | `llm.base_url` | `VP_LLM_BASE_URL=http://localhost:11434` |
| `VP_LLM_API_KEY` | `llm.api_key` | `VP_LLM_API_KEY=ollama` |
| `VP_LLM_MODEL` | `llm.model` | `VP_LLM_MODEL=qwen3:14b` |
| `VPDASH_URL` | `dash.url` | `VPDASH_URL=http://localhost:8765` |
| `VPDASH_TOKEN` | `dash.token` | `VPDASH_TOKEN=s3cret` |
| `VPDASH_CONTAINER_URL` | `dash.container_url` | `VPDASH_CONTAINER_URL=http://vibepod-dash:8765` |
| `VP_CONFIG_DIR` | *(config root)* | `VP_CONFIG_DIR=/custom/path` |
| `VP_PROFILE` | `profile` | `VP_PROFILE=work` |

Expand Down
188 changes: 188 additions & 0 deletions docs/dash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
# Dash integration

[VibePod Dash](https://github.com/VibePod/vibepod-dash) is a small web
dashboard for the state of your running agents — the same idea as the
[herdr](herdr.md) sidebar, but in a browser, so you can check on your agents
from your phone.

Point VibePod at a dash server and every `vp run` and `vp task create` shows up
on the board with a live state (working / blocked / idle / done / error):

```bash
export VPDASH_URL=http://localhost:8765
export VPDASH_TOKEN=<ingest token printed by the dash server>
vp run claude
```

Or, permanently, in `~/.config/vibepod/config.yaml` (or the project's
`.vibepod/config.yaml`):

```yaml
dash:
url: http://localhost:8765
token: s3cret
```

## What VibePod wires up

- `VPDASH_*` environment is injected into the container, including a stable
agent id and the display name `vp:<agent> · <project>`
- for agents with lifecycle hooks, a VibePod-managed reporter plus hook script
is copied into the agent's config dir and registered
- the CLI itself reports the container's start and stop, so **every** agent
appears on the board even without hooks
- `vp stop`, `vp task cancel` and a finished task mark the card done

Built-in hook reporting ships for **claude**, **codex** and **copilot**. The
hooks need only `curl` inside the image.

| Claude hook event | reported state |
| ------------------ | ----------------------- |
| `SessionStart` | `idle` |
| `UserPromptSubmit` | `working` (your prompt) |
| `PreToolUse` | `working` (tool name) |
| `PostToolUse` | `working` (tool name) |
| `Notification` | `blocked` (the message) |
| `Stop` | `idle` |
| `SessionEnd` | `done` |

`blocked` is the state worth a phone notification: the agent is waiting for
your approval.

Codex is registered through `.codex/hooks.json`, so Dash and herdr can both
receive the same lifecycle events. Its mapping is:

| Codex hook event | reported state |
| ------------------ | ----------------------- |
| `SessionStart` | `idle` |
| `UserPromptSubmit` | `working` (your prompt) |
| `PreToolUse` | `working` (tool name) |
| `PostToolUse` | `working` (tool name) |
| `PermissionRequest` | `blocked` (the request) |
| `Stop` | `idle` |
| `Interrupt` | `idle` |
| `SessionEnd` | `done` |

Codex may ask you to trust newly registered hooks once. Review them with
`/hooks` in Codex and approve the VibePod-managed commands.

## What lands on the card

Beyond the state, VibePod attaches the context only it knows. Tap a card on
the dashboard and it shows:

| Detail | Where it comes from |
| ----------- | ------------------------------------------------------- |
| `dir` | the workspace directory mounted into the container |
| `image` | the resolved agent image, overlay included |
| `profile` | the active [credential profile](profiles.md) |
| `container` | the container name — the handle for `vp attach` / `vp stop` |
| `task` | the task id, for `vp task logs` / `vp task cancel` |
| `vibepod` | the CLI version that started the run |

The agent's own hooks add the live part: the prompt you sent, the tool being
run, the notification text it is blocked on. Details are sent once at start
and stay on the card — a hook report that carries none never clears them.

## Reaching the dashboard from inside the container

`localhost` inside a container is the container itself, so a `localhost` or
`127.0.0.1` dash URL is rewritten to `host.docker.internal` for the agent —
VibePod maps that name to the host gateway on every run. The CLI keeps using
the original URL for its own reports. A dash server on another machine (or in
another container) is passed through unchanged.

That rewrite is enough on its own when the dash server's port is published on
the host, and it needs no configuration.

The dash server's own `docker-compose.yml` goes one better: it joins
`vibepod-network` — the network VibePod creates for its containers — under the
alias `vibepod-dash`, so agents can address it by name instead of going back
out through the host, which also survives a rootless Podman setup where the
host gateway is the flakiest part of the chain.

**Either URL works as the only thing you configure.** The CLI reports from the
host, so it cannot use a container-only name; when `dash.url` is one, it falls
back to the same port on `127.0.0.1` — but only after checking that a
dashboard actually answers there. Both of these are complete configurations:

```yaml
dash:
url: http://vibepod-dash:8765 # agents use it as-is; the CLI falls back to
# 127.0.0.1:8765, the published port
```

```yaml
dash:
url: http://localhost:8765 # the CLI uses it as-is; agents get
# host.docker.internal:8765
```

Spell both out when neither default fits — a dashboard on another host, or a
published port that differs from the container's:

```yaml
dash:
url: http://dash.lan:9000 # how the CLI reports
container_url: http://vibepod-dash:8765 # how agents report
```

If you run agents on a custom network (`network:` in the config), put the dash
container on that one — its compose file reads `VIBEPOD_NETWORK`.

## Identity on the board

One card per agent *and* workspace: the id is derived from host, agent and
workspace path, so re-running an agent in the same checkout updates the card it
had before instead of stacking up a new one. Override it per run with
`VPDASH_AGENT_ID` (one card per run) or rename the card with
`VPDASH_AGENT_NAME`.

## Opting out

- `vp run <agent> --no-dash` / `vp task create ... --no-dash` — skip one run
- `dash: false` (or `dash: {enabled: false}`) in the config — disable entirely
- no URL configured — nothing is wired up at all, which is the default

## Custom agents

Like herdr, file injection is data-driven. To wire an agent without built-in
support, map host files into the agent's config directory:

```yaml
dash:
url: http://localhost:8765
integrations:
gemini:
- source: ~/.config/my-hooks/gemini-dash.sh
dest: hooks/gemini-dash.sh
```

Inside the container the script finds `VPDASH_URL`, `VPDASH_TOKEN`,
`VPDASH_AGENT`, `VPDASH_AGENT_ID`, `VPDASH_AGENT_NAME`, `VPDASH_HOST` and
`VPDASH_LOG` in the environment. Reporting is one HTTP call:

```sh
curl -sS -X POST "$VPDASH_URL/api/v1/events" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $VPDASH_TOKEN" \
-d '{"agent_id":"'"$VPDASH_AGENT_ID"'","state":"working","message":"…"}'
```

## Troubleshooting

`vp doctor dash` prints the resolved configuration, checks that the board
answers, and summarises every agent's integration state. `vp doctor dash
<agent>` goes deeper: injected files, registration, the hook trace log
(`<agent config dir>/dash-hook.log`), a live host-side report, and a probe that
runs the real hook inside the agent image, on the same network a real run uses
— which is what proves the container can reach the dashboard, by name or
through the host gateway.

## Limitations

- Reports are HTTP calls from inside the container; an agent image without
`curl` can only be tracked by the CLI-side start/stop reports.
- The dashboard sees whatever the agent reports — prompts, tool names,
notification text. Treat it as sensitive as the sessions it watches, and put
it behind a token (and TLS) when it is reachable beyond your LAN.
6 changes: 6 additions & 0 deletions docs/herdr.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ no documented hook or extension API for live state transitions.
No setup is needed. Detection uses `HERDR_ENV=1`, which herdr sets only
inside its panes.

Codex uses `.codex/hooks.json`, allowing the herdr and Dash integrations to
coexist. It reports `working` for prompts and tool activity, `blocked` for
permission requests, and `idle` for session start, stop, interruption, and
session end. Codex may show a one-time trust prompt for the registered hooks;
use `/hooks` to review and approve the VibePod-managed command.

## Opting out

- `vp run <agent> --no-herdr` — skip wiring for one run
Expand Down
Loading
Loading