Skip to content

Contributing

jstuart0 edited this page Apr 24, 2026 · 1 revision

Contributing

AgentPulse is early — a small cohesive codebase where each feature touches 3–8 files. That's a great place to learn Bun + Hono + Drizzle + React 19 by shipping real features, and a frustrating place to drop a monster PR that touches 80 files. Both can happen; pick one.

Before you start

  • 10 good first issues are tagged with file paths + acceptance criteria. Start with those.
  • Open-source contributions of any size are welcome — small ones land fast, big ones need a short design comment on the issue first.

Dev setup

# Requires Bun 1.x and Node 20+ for the Vite dev server proxy.
git clone https://github.com/jstuart0/agentpulse
cd agentpulse
bun install

# Local SQLite DB at ./data/agentpulse.db
bun run dev

bun run dev starts the API (port 3000) + Vite dev server (port 5173) concurrently. The frontend proxies /app-api/v1/* to the API so cookie auth works.

For the AI control plane to actually work, also set:

export AGENTPULSE_AI_ENABLED=true
export AGENTPULSE_SECRETS_KEY=test-key-32-chars-long-dev-only-yeah

Commands you'll run

bun run dev API + Vite dev server
bun run dev:server API only
bun run typecheck tsc --noEmit — pre-commit sanity
bun test Full test suite
bunx biome check --fix src/ Lint + auto-fix
bun run build Production build (catches Vite errors)

Repo layout

src/
├── server/
│   ├── routes/            HTTP endpoints (Hono routers)
│   ├── services/          Business logic
│   │   ├── ai/            Watcher, LLM adapters, classifier, inbox, digest
│   │   ├── ask/           Global Ask chat (resolver, context, service)
│   │   ├── channels/      Notification channels (Telegram today)
│   │   └── *.ts           Session tracker, name generator, telemetry, …
│   ├── db/                Drizzle schema + client + migrations
│   ├── auth/              API key, Authentik header, local session, supervisor token
│   └── ws/                WebSocket handler with pub/sub
├── web/
│   ├── pages/             Top-level routes (Dashboard, SessionDetail, Ask, …)
│   ├── components/        Reusable UI
│   ├── stores/            Zustand stores (sessions, events, labs, user, prefs, tabs)
│   ├── hooks/             useSessions, useWebSocket
│   └── lib/               API client, theme, utils
├── shared/                Types shared between server + web
└── supervisor/            Per-machine capability + launch reporting (early)

deploy/k8s/                Reference k8s manifests
scripts/                   setup.sh, setup-relay.sh, relay.ts, statusline.sh, …
telemetry-worker/          Cloudflare Worker (opt-out usage pings)
thoughts/                  Research + plans (not checked into public repo history)

Conventions

  • Biome for formatting (tabs, double quotes, semicolons). Run bunx biome check --fix before committing.
  • Drizzle schema — every new table goes in src/server/db/schema.ts AND gets a matching CREATE TABLE IF NOT EXISTS in src/server/db/client.ts. Migrations for existing deploys are ALTER TABLE ADD COLUMN lines in the migrations array (append-only; idempotent; retries on lock).
  • No floating promises — use void foo() or await.
  • Tests live next to codefoo.test.ts beside foo.ts. Use the shared src/server/services/ai/__test_db.ts helper for tests that need a real DB.
  • Hook ingest must be fast — < 50ms response, always return 200. Real work goes into the event processor which runs async after the response.
  • SQLite datetimeYYYY-MM-DD HH:MM:SS with no T/Z. Use parseDate() in the frontend.
  • Session names generated from adjective-noun pairs (name-generator.ts). Don't expose raw UUIDs in UI copy.
  • Feature flags — Labs flags (labs-service.ts) gate UI visibility. AI runtime toggles gate behavior. AGENTPULSE_AI_ENABLED gates whether the feature is compiled in at all.
  • Secrets encrypted at rest — anything sensitive (LLM keys, bot tokens, webhook secrets) goes through services/ai/secrets.ts (AES-256-GCM with scrypt-derived keys). Store in notification_channels.credential_ciphertext or settings JSON blobs.

Adding a new LLM adapter

See good-first-issue #1 (Mistral) for the walkthrough. Short version:

  1. New file under src/server/services/ai/llm/<name>.ts implementing LlmAdapter.
  2. Add the kind to ProviderKind in types.ts.
  3. Wire it into registry.ts's getAdapter().
  4. Pricing in pricing.ts.
  5. UI dropdown entry in AiSettingsPanel.tsx.
  6. Tests mocking happy-path + a 5xx + auth failure.
  7. If the provider supports streaming, implement completeStream() for Ask.

Adding a new notification channel

See good-first-issue #2 (Slack) for the walkthrough. Short version:

  1. New file under src/server/services/channels/<name>.ts implementing NotificationChannelAdapter.
  2. Add the kind to NotificationChannelKind in channels/types.ts.
  3. Wire into the registry + channel routes.
  4. Build a <Name>ChannelPanel.tsx modeled on TelegramChannelPanel.tsx.
  5. If the channel has interactive callbacks (button taps), add a signed webhook route outside the api auth bundle.
  6. Tests for signature verification + message formatting.

Design notes — things that will save you time

  • The api Hono sub-bundle gets mounted under both /api and /app-api. Public routes (webhooks, hooks) must be mounted outside it because sibling routers' .use("*", requireAuth()) cascade across the whole bundle when merged via api.route().
  • When returning streaming responses, don't use Hono's streamSSE helper — it sets Transfer-Encoding: chunked, forbidden on HTTP/2. Use a plain new Response(readableStream, { headers }) with Content-Type: text/event-stream.
  • The watcher should never subscribe to session_updated — every hook ingest and the 60-second stale sweeper emit that event, flooding the queue. Use session_event only.

PR workflow

  1. Fork, branch from dev (not main).
  2. Ship the smallest unit that makes sense as a PR. 3–8 files is great. 50+ files needs a design comment on the issue first.
  3. Write tests for the happy path + the edge case you thought about while writing the feature.
  4. Biome clean, typecheck clean, tests green locally.
  5. Open the PR against dev. CI runs the same checks.
  6. Maintainer review usually within 48h.

Code of conduct

Be nice. Be specific. Ship small. Reality-check your own PRs before submitting — if you wouldn't review this in 10 minutes, break it up.

Clone this wiki locally