Command center for AI coding agents across all your machines.
If you run multiple Claude Code or Codex CLI sessions across different terminal tabs, you know the pain: which tab is doing what? AgentPulse gives you a live dashboard that shows every active session, what it's working on, and a scrollable chat history of everything you've said to each agent.
AgentPulse has two major modes:
- Observability -- watch Claude Code and Codex sessions in real time, with prompts, responses, progress, notes, and session history in one dashboard
- Orchestration -- launch and manage sessions from AgentPulse itself with templates, supervisors, headless tasks, interactive sessions, retries, and host routing
Plus an AI Labs layer that's very new and explicitly experimental -- see the Labs section below.
You can run AgentPulse as:
- observability only -- hooks + dashboard, no supervisor/control plane
- full local orchestration -- hooks + dashboard + local supervisor for launch/control on the same machine
- remote dashboard -- relay local events to a remote AgentPulse instance
Your terminal tabs AgentPulse dashboard
┌─────────────────┐ ┌──────────────────────┐
│ Claude Code (1) │──── hook events ──────>│ bold-falcon: active │
│ fixing auth bug │ │ "fix the auth bug" │
├─────────────────┤ ├──────────────────────┤
│ Claude Code (2) │──── hook events ──────>│ zen-owl: active │
│ writing tests │ │ "add unit tests" │
├─────────────────┤ ├──────────────────────┤
│ Codex CLI │──── hook events ──────>│ warm-crane: idle │
│ (idle) │ │ last: 5m ago │
└─────────────────┘ └──────────────────────┘
Each session gets a random memorable name (like bold-falcon) so you can match the dashboard to your terminal tabs at a glance. Click any session to see a live chat-style timeline of your prompts and the agent's tool usage.
macOS / Linux:
This installs AgentPulse locally with Bun + SQLite, starts the web app and local supervisor as services, and configures Claude Code + Codex hooks automatically.
curl -fsSL https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.sh | bashWindows:
irm https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.ps1 | iexWhen it finishes, open http://localhost:3000 and start a new Claude Code or Codex session.
If you prefer Docker, this starts the container, waits for health, and configures hooks:
docker run -d -p 127.0.0.1:3000:3000 -v agentpulse-data:/app/data -e DISABLE_AUTH=true --restart unless-stopped --name agentpulse ghcr.io/jstuart0/agentpulse && until curl -fsSL http://localhost:3000/api/v1/health >/dev/null 2>&1; do sleep 1; done && curl -sSL http://localhost:3000/setup.sh | bashSecurity note:
-p 127.0.0.1:3000:3000binds the host port to localhost only. The older-p 3000:3000form would publish on all host interfaces, exposing the auth-disabled server to your LAN. Use the127.0.0.1:prefix wheneverDISABLE_AUTH=true.
Done. Open http://localhost:3000 and you have:
- live session observability
- local launch/control via the supervisor
- Claude Code + Codex hooks already configured
Why localhost? Claude Code and Codex block HTTP hooks to remote/private IPs as a security measure. Only
localhost/127.0.0.1is allowed. This keeps things simple -- one Docker container on your machine, no networking to configure. If port 3000 is taken, use any free port:docker run -d -p 127.0.0.1:4000:3000 -v agentpulse-data:/app/data -e DISABLE_AUTH=true -e PUBLIC_URL=http://localhost:4000 --restart unless-stopped --name agentpulse ghcr.io/jstuart0/agentpulse curl -sSL http://localhost:4000/setup.sh | bash
- Dashboard -- grid of all sessions with status, project name, session name, duration, and tool use count
- Session detail -- click a session to see a chat-style timeline with your prompts as blue bubbles and tool usage inline
- Projects -- first-class projects with cwd-based session resolution; sessions stamp themselves with the right project on ingest, templates inherit project defaults (cwd, agentType, model) with per-field overrides, and a
/projectspage lets you create / edit / delete them. Saving a template under a new directory auto-creates the project for you - Session templates -- save reusable Claude Code and Codex session setups, link them to a project for live-inheritance defaults, preview normalized launch specs, and route launches to the right host
- Orchestration -- launch headless or interactive sessions from AgentPulse, track launch status, retry, stop, and manage sessions through the local supervisor
- Search -- full-text across session names, prompts, plans, notes, and event payloads. SQLite uses FTS5 (BM25-ranked); Postgres uses ILIKE. Clicking an event hit jumps to the matching event in the timeline and applies a brief amber flash
- Inbox -- single
/inboxview that aggregates every open approval (HITL, Ask-driven actions, alert-rule firings), stuck / risky session warnings, and recent failures. Approve / decline inline; snooze noisy items - Real-time updates -- everything updates live via WebSocket, no refreshing needed
- Random session names -- each session gets a name like
brave-falconso you can tell them apart - CLAUDE.md editor -- view and edit your agent instruction files from the dashboard
- Setup page -- generates hook config you can copy-paste, or use the one-liner above
- AI Labs (experimental) -- optional AI layer that watches sessions, classifies health, proposes next steps with human-in-the-loop approval, plus an Ask command surface that lets you launch / edit / search / summarize / alert in natural language. Each feature is behind its own Labs toggle. See below.
Heads up: the AI layer is new, shipping under explicit Labs framing. Every feature is toggleable under Settings → Labs. Contracts, UI, and defaults may change. Disable any toggle if it gets in your way -- nothing else in AgentPulse depends on it.
When enabled, AgentPulse can use an LLM provider you choose (Anthropic, OpenAI, OpenRouter, Google, or any OpenAI-compatible endpoint like Ollama / LM Studio / vLLM) to do the following. All AI work is human-in-the-loop by default: the watcher proposes, you approve or decline, and the runtime records every step as an auditable event.
- Session watcher -- on each handoff (a
Stopevent, idle pause, plan completion, or error), the watcher reads recent events, redacts secrets via a configurable rule list, and asks the configured provider to emit one JSON decision:continue(with a next prompt),ask(route to HITL),report(summarize),stop, orwait. Proposals land in a durable queue so they survive server restarts. - Per-session config -- provider, policy (
ask_always/ask_on_risk/auto), daily spend cap, max continuations, optional custom system prompt, all from the session detail AI tab. - Auto-enable on Ask-initiated sessions -- when Ask launches a session, a watcher row is attached at correlation time (enabled,
ask_on_riskpolicy, default provider). Five silent-skip branches keep this from ever failing a launch: not Ask-initiated, AI inactive, user opt-out, watcher already configured, or no default provider. Settings → AI watcher has a toggle (default on) to disable. - Session intelligence classifier -- deterministic heuristic flags sessions as
healthy/blocked/stuck/risky/complete_candidatewith a one-sentence reason. Shows as a chip on dashboard cards. Optionally feeds back into watcher decisions. - Ask command surface -- conversational interface (web + Telegram) that turns natural language into approval cards. Every state-mutating intent goes through the same
ai_action_requestsatomic-claim approval pipeline, so concurrent web + Telegram approvals can't double-execute. Examples:- "open a Claude session for agentpulse" — queues a launch request
- "add a project myapp at /tmp/myapp" — multi-turn drafting walks you through missing fields
- "resume brave-falcon with: refactor the auth module" — new launch in the same cwd with the new prompt; the inbox card shows "Resume of brave-falcon" so the approver sees the parent
- "pin brave-falcon" / "add a note to slate-bear: needs review" / "rename auth-worker to auth-refactor" — non-destructive direct execute, with the resolved session name embedded in the reply
- "stop the auth session" / "archive completed sessions on agentpulse" / "delete template auth-setup" — destructive actions go through approval; bulk targets are previewed (cap 50, "+N more" footer)
- "alert me when any session on agentpulse fails" / "alert when the agent mentions a security concern" — creates a project-level alert rule (constrained or freeform) with per-rule daily token budget
- "set up a Telegram channel called personal" — creates a pending notification channel and returns the enrollment code
- "summarize session brave-falcon" / "why did session amber-wolf fail" — bounded-transcript Q&A with provenance footer; cached for 15 minutes per
(session, normalized question)and invalidated by new events - "show me failed sessions" / "what happened today" — read-only NL search and digest; both heuristic-only (no LLM call), so they're fast and free
- Operator inbox -- single
/inboxview that aggregates open HITL requests, Ask-driven action requests, stuck / risky sessions, and recently failed proposals across every session and project. Approve / decline inline, snooze noisy failed proposals for 1h / 4h / 24h / 7d, or batch decline. - Project digest --
/digestrolls up the last 24 hours of activity grouped by working directory: active / blocked / stuck / completed counts per repo, top plan completions, notable failures. Cached daily, manual refresh available. - Project alert rules -- per-project rules that fire when sessions transition (
status_failed,status_completed,status_stuck,no_activity_minutes) or when a freeform LLM-evaluated condition matches an event. Evaluation runs inWatcherRunner's 60-second sweep with re-entry guard and first-run backfill (so a newstatus_stuckrule on a project with thirty already-stuck sessions doesn't notification-storm). Freeform rules carry their own daily token budget so cost stays bounded. - Template distillation (API only) --
POST /api/v1/ai/templates/distillgenerates a reviewableSessionTemplateInputdraft from a successful session, with provenance metadata. - Launch recommendation (API only) --
POST /api/v1/launches/recommendationreturns an advisory agent + model + host suggestion based on prior completions at the same cwd. The existing launch validator is still the resolver of record. - Risk classes + ask_on_risk (API only) -- configurable list of risk matchers (destructive command patterns, credential references, recent test failures) that escalate a proposed
continueto HITL regardless of policy. SeeGET /api/v1/ai/risk-classes. - Guarded
autopolicy -- dispatch without HITL only when the session is managed, the supervisor is connected, no risk class matched, and the dispatch-filter accepts the prompt. Every other case still routes to HITL. Everything is auditable viaai_continue_sentevents. - Spend + kill switch -- per-user daily spend cap, per-rule cap for freeform alert rules, a global kill switch in Settings that immediately pauses all watchers, and HITL / action requests carry optional timeouts that auto-expire if ignored.
- Local-model thinking suppression -- when the configured provider is OpenAI-compatible (Ollama, vLLM, llama.cpp), classifier calls send
reasoning_effort: "none"so qwen3 and similar reasoning models return clean JSON instead of burying the response in chain-of-thought. Anthropic / OpenAI / Google / OpenRouter providers receive prompts unchanged. - Observability -- every wake emits a structured JSON log line prefixed with
ai_metric(watcher run queued / completed, HITL resolution latency, classifier distribution, etc.). Pipe them into Loki / Datadog / Splunk / Elastic. Optional OTLP forwarding viaAGENTPULSE_OTEL_ENDPOINT. A/api/v1/ai/diagnosticsendpoint returns a point-in-time queue and flag snapshot for in-dashboard viewing.
AI is gated by two build-time env vars and a runtime toggle:
# Compile AI in at boot
AGENTPULSE_AI_ENABLED=true
# 32+ character random string used to encrypt provider credentials at rest.
# Required whenever AGENTPULSE_AI_ENABLED=true; AgentPulse refuses to start otherwise.
AGENTPULSE_SECRETS_KEY=<your-random-string>
# Optional: forward ai_metric log events to an OTLP-compatible collector
AGENTPULSE_OTEL_ENDPOINT=https://otel.example.com/v1/metricsOnce those are set, open Settings → AI watcher, add a provider (an API key, or a local Ollama / LM Studio URL -- no key needed), then flip AI enabled on. AI work does not start until you also enable the watcher per-session from the session AI tab.
Each AI surface has its own Labs toggle under Settings → Labs:
| Flag | Default | Effect when off |
|---|---|---|
inbox |
on | Hides the /inbox nav link |
digest |
on | Hides the /digest nav link |
aiSessionTab |
on | Hides the AI tab in session detail |
intelligenceBadges |
on | Hides the health chip on dashboard session cards |
aiSettingsPanel |
on | Hides the entire AI watcher section in Settings |
askAssistant |
on | Disables the Ask command surface (/ai/ask and Telegram inbound) — falls back to read-only dashboard |
templateDistillation |
off | Experimental, API-only for now |
launchRecommendation |
off | Experimental, API-only for now |
riskClasses |
off | Experimental, API-only for now |
telegramChannel |
off | Forward HITL requests to a Telegram chat with inline Approve / Decline buttons (requires TELEGRAM_BOT_TOKEN + TELEGRAM_WEBHOOK_SECRET) |
Direct URLs (/inbox, /digest, etc.) stay reachable when a flag is off -- toggling a flag hides it from the nav, not from bookmarks.
When enabled, the watcher can forward HITL requests to a Telegram chat with inline Approve / Decline buttons instead of (or in addition to) the in-app inbox. Useful for approving continuations from your phone while an agent is running somewhere else.
Enable it:
- Create a bot with @BotFather and get the bot token.
- Generate a webhook secret (
openssl rand -hex 24) and set both env vars:TELEGRAM_BOT_TOKEN=<token from BotFather> TELEGRAM_WEBHOOK_SECRET=<≥24 random chars>
- Restart AgentPulse. Open Settings → Labs and flip
telegramChannelon. - A new Telegram HITL channel section appears in Settings. Click Set webhook once — it points Telegram at
PUBLIC_URL/api/v1/channels/telegram/webhook. - Click Generate code. Copy the
/start <code>shown and DM it to your bot. The bot confirms the link. - On any session, open the AI tab and assign that channel in the watcher config.
From then on, any HITL that watcher opens for that session is sent to Telegram. Tapping Approve routes through the same HITL-resolve path as the in-app button: the ai_hitl_response and ai_continue_sent events fire identically, just annotated with channel: telegram.
Safety notes:
- The bot token is instance-wide; every chat that's enrolled via
/startshares the same bot. The chat id itself is encrypted at rest withAGENTPULSE_SECRETS_KEY. - The webhook route validates Telegram's
X-Telegram-Bot-Api-Secret-Tokenheader againstTELEGRAM_WEBHOOK_SECRETon every request, so a lucky guesser still can't forge approvals. - An approval tapped in Telegram is cross-checked against the HITL row's
channel_idbefore any resolve happens — a user who learns a HITL id cannot use a different chat to act on it. - Delivery failures never block the in-app HITL path. If Telegram is down or slow, approve/decline still works from the dashboard or
/inbox.
- HITL by default -- a Claude/Codex watcher can propose, but
auto-dispatch only runs when the session is managed and the supervisor is connected. - Dispatch filter -- every prompt (watcher-proposed or user-approved) is screened against a deny-list of destructive / injection-flavored patterns before dispatch.
- Redactor -- transcripts are scrubbed of common secret patterns before being sent to any provider, with a dry-run preview available.
- Prompt injection hardening -- user transcripts are embedded in an explicit
<transcript>UNTRUSTED block with instructions for the model to treat the contents as data. - Kill switch -- flipping the single kill-switch setting pauses every watcher instantly; no per-session unwinding needed.
Recommended for most OSS users. No Docker, no Kubernetes. This is the full single-machine setup: dashboard, hooks, and local supervisor/control plane.
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.sh | bashWindows:
irm https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.ps1 | iexWhat it does:
- installs Bun if needed
- clones AgentPulse to
~/.agentpulse/app - builds the app
- stores SQLite data in
~/.agentpulse/data - starts AgentPulse as a local service
- macOS:
launchd - Linux:
systemd --userwhen available
- macOS:
- writes
~/.agentpulse/supervisor.json - starts the local supervisor service on the same machine
- configures Claude Code + Codex hooks automatically when auth is disabled or an API key is provided
- gives you local live-session control without extra manual setup on the same machine
Useful options:
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.sh | bash -s -- \
--port 4000 \
--public-url http://localhost:4000 \
--data-dir "$HOME/.agentpulse/data"Windows:
iwr https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.ps1 -OutFile "$env:TEMP\install-local.ps1"
powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-local.ps1" -Port 4000 -PublicUrl http://localhost:4000 -DataDir "$HOME\.agentpulse\data"If you want auth enabled from the start:
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.sh | bash -s -- \
--disable-auth false \
--api-key ap_your_key_hereWindows:
iwr https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.ps1 -OutFile "$env:TEMP\install-local.ps1"
powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-local.ps1" -DisableAuth:$false -ApiKey ap_your_key_hereIf you only want observability and do not want the local supervisor/control plane:
macOS / Linux:
curl -fsSL https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.sh | bash -s -- --skip-supervisorWindows:
iwr https://raw.githubusercontent.com/jstuart0/agentpulse/main/scripts/install-local.ps1 -OutFile "$env:TEMP\install-local.ps1"
powershell -ExecutionPolicy Bypass -File "$env:TEMP\install-local.ps1" -SkipSupervisorThat observability-only mode still gives you:
- live session monitoring
- prompts, responses, and progress in the dashboard
- notes and instruction-file editing
- remote viewing if you later use the relay path
Best if you already use Docker locally.
docker run -d -p 127.0.0.1:3000:3000 -v agentpulse-data:/app/data -e DISABLE_AUTH=true --restart unless-stopped --name agentpulse ghcr.io/jstuart0/agentpulse
curl -sSL http://localhost:3000/setup.sh | bashSecurity note:
-p 127.0.0.1:3000:3000binds the host port to localhost only whenDISABLE_AUTH=true. Use this form for local-only Docker; the older-p 3000:3000shorthand publishes on all host interfaces.
Best if you want to monitor sessions from other devices while your agents still run on your laptop/workstation.
Use the relay installer:
curl -sSL https://your-server.com/setup-relay.sh | bash -s -- --key ap_YOUR_KEYThat installs a local relay on localhost:4000, configures hooks automatically, and forwards events to your remote AgentPulse server.
If you want to access AgentPulse from any device on your network (phone, tablet, another machine) while still collecting events from your local agents:
Architecture:
Your Mac Your server / k8s cluster
┌──────────────────────┐ ┌──────────────────────────┐
│ Claude Code / Codex │ │ AgentPulse (remote) │
│ hooks → localhost │ │ https://pulse.mynet.com │
│ │ │ Forwardauth IdP (SSO) │
│ local relay │─── hooks ────────>│ SQLite or Postgres │
│ forwards events │ │ │
└──────────────────────┘ │ Browse from any device │
└──────────────────────────┘
Option A: Local with LAN access (auth enabled)
Never combine
-p 0.0.0.0:3000:3000with-e DISABLE_AUTH=true. That combination exposes all mutation APIs to anyone on the network with zero credentials. If you need LAN access, use the auth-enabled config below. If you needDISABLE_AUTHfor local convenience, use-p 127.0.0.1:3000:3000so the port is not published on network interfaces.
Run AgentPulse on your machine, bind to 0.0.0.0 so other devices on your LAN can view the dashboard. Auth is required — create a local admin via the bootstrap env vars:
docker run -d -p 0.0.0.0:3000:3000 -v agentpulse-data:/app/data \
-e HOST=0.0.0.0 \
-e AGENTPULSE_LOCAL_ADMIN_USERNAME=admin \
-e AGENTPULSE_LOCAL_ADMIN_PASSWORD=<strong-password> \
--restart unless-stopped --name agentpulse ghcr.io/jstuart0/agentpulse
curl -sSL http://localhost:3000/setup.sh | bash -s -- --key ap_YOUR_API_KEY
# Dashboard: http://localhost:3000 (local) or http://your-ip:3000 (LAN)The default config requires login via the dashboard. DO NOT add -e DISABLE_AUTH=true on any network you do not fully control.
Option B: Remote server with local relay (recommended for k8s/VPS)
Run AgentPulse on a server you can access from anywhere -- your phone, tablet, another machine. Check on long-running agent tasks while you're away from your desk. See if that 30-minute refactor finished, whether an agent hit an error, or what all your sessions are working on -- without being at your computer.
Multiple machines can report to the same dashboard. Run the relay setup on your MacBook, your Linux build server, a cloud VM -- every agent session across all your machines shows up in one place. One dashboard to rule them all.
One command sets up everything, no repo clone needed:
curl -sSL https://your-server.com/setup-relay.sh | bash -s -- --key ap_YOUR_KEYThat single command:
- Installs Bun if you don't have it
- Installs a tiny relay at
~/.agentpulse/relay.ts - Creates a macOS LaunchAgent (or Linux systemd service) that auto-starts on login
- Configures Claude Code + Codex hooks to point at
localhost:4000 - Starts the relay immediately
Your agents send events to localhost:4000 (allowed by Claude Code), the relay forwards them to your remote server. Open the dashboard from any device to monitor your agents in real time.
Manage the relay:
Stop: launchctl unload ~/Library/LaunchAgents/dev.agentpulse.relay.plist
Start: launchctl load ~/Library/LaunchAgents/dev.agentpulse.relay.plist
Logs: tail -f ~/.agentpulse/logs/relay.log
Config: cat ~/.agentpulse/config.json
Option C: Kubernetes with forwardauth SSO (Authentik / Authelia / oauth2-proxy / Pomerium / Cloudflare Access)
See deploy/k8s/ for full manifests including Traefik IngressRoute with split auth (hooks bypass
SSO, dashboard is forwardauth-protected). The IngressRoute has separate rules so /api/v1/hooks
uses API key auth while everything else goes through your forwardauth IdP.
The homelab example uses Authentik; other providers work via env config — set
FORWARDAUTH_PROVIDER and the FORWARDAUTH_HEADER_* vars to match your IdP's headers.
See deploy/k8s/FORWARDAUTH.md for provider-specific setup instructions.
By default, AgentPulse generates an API key on first start (printed in server logs). Pass it to the setup script:
curl -sSL http://localhost:3000/setup.sh | bash -s -- --key ap_YOUR_KEYFor local use where you don't need auth, set DISABLE_AUTH=true (as shown in quick start).
If AgentPulse runs on a different machine:
curl -sSL https://your-server.com/setup.sh | bash -s -- --url https://your-server.com --key ap_YOUR_KEYSQLite (stored at ./data/agentpulse.db) is the default. Zero-config, single-file, handles home-lab and small-team scale comfortably.
PostgreSQL is supported as of v0.4.0 for production and multi-replica deployments. Set DATABASE_URL=postgres://user:password@host:5432/dbname and AgentPulse uses Postgres instead of SQLite. See Production / multi-replica with Postgres below.
Limitations in this release: vector search (event_embeddings) remains SQLite-only (pgvector port is a follow-up); search on Postgres uses ILIKE rather than tsvector (adequate for moderate event volumes; tsvector migration is a follow-up for high-volume deployments). There is no SQLite→Postgres data migrator — Postgres installs start fresh.
AgentPulse ships a Content-Security-Policy-Report-Only header (as of 0.3.0). This surfaces CSP violations in POST /api/v1/csp-report (structured JSON log) without blocking anything. Enforcement mode (Content-Security-Policy) will follow in a future release once reports are clean in production.
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
Server port |
HOST |
127.0.0.1 |
Bind address. The published Docker image overrides this to 0.0.0.0 via ENV HOST=0.0.0.0; bare bun run start binds localhost only. |
PUBLIC_URL |
http://localhost:3000 |
Public URL (used in setup script) |
DATA_DIR |
./data |
Base directory for local SQLite storage |
SQLITE_PATH |
${DATA_DIR}/agentpulse.db |
Override the SQLite database file path |
DISABLE_AUTH |
false |
Skip all authentication |
AGENTPULSE_ALLOW_SIGNUP |
false |
Allow open signup on an empty instance. Set true to enable the first-run signup flow. Once any user exists, signup is blocked regardless. |
FORWARDAUTH_TRUST_SECRET |
Shared secret for the forwardauth header trust gate (k8s SSO deployments). Generate with openssl rand -hex 32. See deploy/k8s/FORWARDAUTH.md. Legacy alias AGENTPULSE_AUTHENTIK_TRUST_SECRET accepted for one release. |
|
FORWARDAUTH_PROVIDER |
authentik |
Forwardauth provider label. Appears in the dashboard UI and /auth/me response. Only "authentik" triggers the Authentik sign-out URL; other values render the provider name and return signOutUrl: null. |
FORWARDAUTH_HEADER_USERNAME |
X-Authentik-Username |
Header carrying the authenticated username from the upstream IdP. |
FORWARDAUTH_HEADER_EMAIL |
X-Authentik-Email |
Header carrying the authenticated email address. |
FORWARDAUTH_HEADER_GROUPS |
X-Authentik-Groups |
Header carrying group memberships. |
FORWARDAUTH_HEADER_NAME |
X-Authentik-Name |
Header carrying the user's display name. |
FORWARDAUTH_HEADER_UID |
X-Authentik-Uid |
Header carrying the unique user identifier. |
FORWARDAUTH_HEADER_VERIFY |
X-Authentik-Verify |
Header used to carry the trust secret from Traefik to AgentPulse. |
FORWARDAUTH_HEADER_STRIP_PREFIX |
X-Authentik- |
Prefix of IdP identity headers stripped before forwardauth runs. |
LOG_LEVEL |
info |
debug, info, warn, error |
AGENTPULSE_TELEMETRY |
on |
Set off to disable anonymous telemetry |
DO_NOT_TRACK |
Set 1 to disable telemetry (standard) |
|
AGENTPULSE_TELEMETRY_MODE |
inferred | Telemetry install class override: self_hosted_real, production, dev, test, or ci |
AGENTPULSE_TELEMETRY_TEST |
Set 1 to force this install to report as test |
|
AGENTPULSE_AI_ENABLED |
false |
Compile the AI Labs layer in at boot. Off = zero AI services, routes, or UI (non-AI install footprint is identical to pre-AI). |
AGENTPULSE_SECRETS_KEY |
Required when AGENTPULSE_AI_ENABLED=true. 32+ random chars; encrypts provider credentials at rest (AES-256-GCM). |
|
AGENTPULSE_OTEL_ENDPOINT |
Optional OTLP metrics endpoint. When set, ai_metric log events are also forwarded as OTLP. |
|
DATABASE_URL |
"" (SQLite) |
Postgres connection string. When set to a postgres://... URL, AgentPulse uses PostgreSQL instead of SQLite. Example: postgres://agentpulse:password@host:5432/agentpulse?sslmode=require. Leave unset or empty for SQLite. |
AGENTPULSE_PG_POOL_MAX |
10 |
Maximum Postgres connection pool size. Integer in [1, 100]. Tune based on your Postgres server's max_connections and replica count. |
AGENTPULSE_LEGACY_INIT |
(unset) | SQLite existing-install migration behaviour. Set to "false" to force a fresh Drizzle migrate on an existing SQLite install (opt-in, non-destructive if schema is already current). Unset keeps the legacy initializeDatabase() path for existing SQLite installs. |
TELEGRAM_BOT_TOKEN |
Instance-wide Telegram bot token (get one from @BotFather). Required to enable the Telegram HITL channel. | |
TELEGRAM_WEBHOOK_SECRET |
Shared secret Telegram echoes back on every webhook callback. ≥24 random chars. Required when TELEGRAM_BOT_TOKEN is set. |
Telemetry classification defaults:
CI=truereports asci- prerelease/dev builds report as
dev - stable production builds report as
self_hosted_real - set
AGENTPULSE_TELEMETRY_MODE=testorAGENTPULSE_TELEMETRY_TEST=1for local/test deployments so they do not pollute real-world usage counts
Running curl -sSL .../setup.sh | bash configures:
- Claude Code -- adds HTTP hooks to
~/.claude/settings.jsonfor 10 events (SessionStart, Stop, PreToolUse, PostToolUse, etc.) - Codex CLI -- creates
~/.codex/hooks.jsonwith 5 events and enables the hooks feature flag inconfig.toml - Shell -- adds
AGENTPULSE_API_KEYandAGENTPULSE_URLto your.zshrcor.bashrc(if API key provided) - Verify -- sends a test event to confirm connectivity
All hooks use async: true so they never slow down your agents.
launchctl unload ~/Library/LaunchAgents/dev.agentpulse.local.plist
launchctl load ~/Library/LaunchAgents/dev.agentpulse.local.plist
tail -f ~/.agentpulse/logs/agentpulse.out.log
tail -f ~/.agentpulse/logs/supervisor.out.logsystemctl --user restart agentpulse
journalctl --user -u agentpulse -fGet-ScheduledTask AgentPulseLocal
Get-ScheduledTask AgentPulseSupervisor
Get-Content "$HOME\.agentpulse\logs\agentpulse.out.log" -Wait
Get-Content "$HOME\.agentpulse\logs\supervisor.out.log" -Waitcd ~/.agentpulse/app
export $(cat .env.local | xargs)
bun run startManifests are in deploy/k8s/. Includes namespace, deployment, service, PVC, configmap, and Traefik IngressRoute with optional forwardauth SSO (Authentik by default; configurable for Authelia, oauth2-proxy, Pomerium, Cloudflare Access via env vars).
# Create a real Secret out of band (see deploy/k8s/01-secret-template.yaml for the shape)
# then apply the Kustomize base:
kubectl apply -k deploy/k8s/For deployments that need multi-replica scale-out or prefer a managed Postgres instance over a local SQLite file:
# 1. Verify kubectl context
kubectl config current-context
# Expected: your production cluster context
# 2. Create database and user (on your Postgres host)
psql -h your-postgres-host -U psadmin \
-c "CREATE USER agentpulse WITH PASSWORD '<password>';"
psql -h your-postgres-host -U psadmin \
-c "CREATE DATABASE agentpulse OWNER agentpulse ENCODING 'UTF8' \
LC_COLLATE 'C' LC_CTYPE 'C' TEMPLATE template0;"
# 3. Fill in the credentials file (gitignored — do NOT commit)
cp deploy/overlays/postgres/secret-patch.yaml.example \
deploy/overlays/postgres/secret-patch.yaml
# Edit secret-patch.yaml: set DATABASE_URL to the full connection string
# e.g. postgres://agentpulse:<pw>@host:5432/agentpulse?sslmode=require
kubectl apply -f deploy/overlays/postgres/secret-patch.yaml -n agentpulse
# 4. Render and apply
kubectl kustomize deploy/overlays/postgres/ # verify output first
kubectl apply -k deploy/overlays/postgres/AgentPulse runs Drizzle migrations on boot using a dedicated single-connection client. A session-level pg_advisory_lock serializes migration across replicas booting simultaneously, making rolling deploys safe without coordination overhead.
Connection pool size defaults to 10. Tune it via AGENTPULSE_PG_POOL_MAX based on your Postgres max_connections setting and replica count.
The Postgres overlay removes the SQLite backup sidecar (no longer needed). The SQLite PVC is left in place until you confirm data has been migrated or is no longer needed, then delete it manually.
See deploy/overlays/postgres/README.md for the full pre-flight checklist and rollback notes.
git clone https://github.com/jstuart0/agentpulse.git
cd agentpulse
bun install
bun run dev # starts API server + Vite dev server| Command | What it does |
|---|---|
bun run dev |
Start dev server (API + frontend with hot reload) |
bun run build |
Production build |
bun run start |
Start production server |
bun run check |
Lint with Biome |
bun run typecheck |
TypeScript type check |
Bun + Hono + React 19 + TailwindCSS + Drizzle ORM + Zustand + SQLite (default) / PostgreSQL
- 📘 Wiki — Getting Started, Architecture, AI Watcher deep-dive, Ask assistant, Telegram setup, Deployment, FAQ, Roadmap.
- 🧰 Good first issues — 10 starter tickets with file paths + acceptance criteria: new LLM adapters (Mistral, Cohere), new notification channels (Slack, Discord), a11y polish, diagnostic CLI, CI workflow, more test coverage.
- 🐛 Issues — bug reports + feature requests welcome.
- 📜 CHANGELOG — every release, with the reason behind each change.
- 🤝 Contributing guide — dev setup, repo layout, conventions, gotchas.
MIT


