@lightupai/polaris 0.0.76 → 0.0.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,77 @@
1
+ name: CD
2
+
3
+ # Continuous deployment: on every push (merge) to master, SSH to the prod
4
+ # host and run deploy.sh (git pull + docker compose build/up).
5
+ #
6
+ # Required repository secrets (Settings → Secrets and variables → Actions):
7
+ # DEPLOY_HOST — hostname or IP of the prod box (e.g. polaris.lightup.ai)
8
+ # DEPLOY_SSH_KEY — private SSH key (OpenSSH format) for the deploy user
9
+ #
10
+ # Optional repository secrets (deploy.sh has sensible defaults):
11
+ # DEPLOY_USER — SSH user on the prod box (default: deploy)
12
+ # DEPLOY_DIR — checkout directory on the prod box (default: /opt/polaris)
13
+ #
14
+ # If DEPLOY_HOST / DEPLOY_SSH_KEY are not configured, the deploy job is
15
+ # skipped cleanly (check-secrets still succeeds), so this workflow is a
16
+ # safe no-op on forks and on repos where CD has not been set up yet.
17
+
18
+ on:
19
+ push:
20
+ branches: [master]
21
+ workflow_dispatch:
22
+
23
+ # Never run two deploys at once; queue (don't cancel) an in-flight deploy.
24
+ concurrency:
25
+ group: deploy-prod
26
+ cancel-in-progress: false
27
+
28
+ jobs:
29
+ check-secrets:
30
+ runs-on: ubuntu-latest
31
+ outputs:
32
+ configured: ${{ steps.check.outputs.configured }}
33
+ steps:
34
+ # Secrets are not directly readable in a job-level `if`, so expose a
35
+ # boolean output here and gate the deploy job on it.
36
+ - name: Check deploy secrets
37
+ id: check
38
+ env:
39
+ DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
40
+ DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
41
+ run: |
42
+ if [ -n "$DEPLOY_HOST" ] && [ -n "$DEPLOY_SSH_KEY" ]; then
43
+ echo "configured=true" >> "$GITHUB_OUTPUT"
44
+ echo "Deploy secrets present — deploy job will run."
45
+ else
46
+ echo "configured=false" >> "$GITHUB_OUTPUT"
47
+ echo "DEPLOY_HOST / DEPLOY_SSH_KEY not configured — skipping deploy."
48
+ fi
49
+
50
+ deploy:
51
+ needs: check-secrets
52
+ if: needs.check-secrets.outputs.configured == 'true'
53
+ runs-on: ubuntu-latest
54
+
55
+ steps:
56
+ - uses: actions/checkout@v4
57
+
58
+ - name: Set up SSH
59
+ env:
60
+ DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
61
+ run: |
62
+ mkdir -p ~/.ssh
63
+ printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/id_deploy
64
+ chmod 600 ~/.ssh/id_deploy
65
+ {
66
+ echo "Host *"
67
+ echo " IdentityFile ~/.ssh/id_deploy"
68
+ echo " StrictHostKeyChecking accept-new"
69
+ } >> ~/.ssh/config
70
+ chmod 600 ~/.ssh/config
71
+
72
+ - name: Deploy
73
+ env:
74
+ DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
75
+ DEPLOY_USER: ${{ secrets.DEPLOY_USER }}
76
+ DEPLOY_DIR: ${{ secrets.DEPLOY_DIR }}
77
+ run: ./deploy.sh
@@ -0,0 +1,203 @@
1
+ # Polaris — System Architecture
2
+
3
+ *As of branch `feat/knowledge-capture-alpha` (PR #71), 2026-06-11. Grounded in the actual code: `src/service/{server,db,auth}.ts`, `src/daemon/daemon.ts`, `src/client/client.ts`, `src/slack/bridge.ts`, `src/web/app.ts`, `src/cli/cli.ts`, `hooks/*`.*
4
+
5
+ Legend: `──▶` one-way · `◀──▶` two-way (request/response or bidirectional) · `≈≈▶` deferred/not-yet-wired.
6
+
7
+ ---
8
+
9
+ ## 1. What Polaris is, in one paragraph
10
+
11
+ Polaris captures everything a coding agent does (prompts, responses, tool calls) into a durable, org-scoped, searchable record, broadcasts it to teammates in real time, and lets a teammate inject context back into a *live* agent session. It integrates with the **local Claude Code CLI** via two mechanisms — **hooks** (capture out) and an **MCP server** (control + inject delivery) — never with claude.ai or Claude Code web. A central cloud service is the system of record and the real-time broker; Slack is one rendering/participation surface; a web dashboard is the read/search/inject surface.
12
+
13
+ ---
14
+
15
+ ## 2. Where everything runs
16
+
17
+ | Plane | Components |
18
+ |---|---|
19
+ | **Each developer's machine** | Claude Code CLI · Polaris hooks (`capture.sh`, `capture-stop.ts`, `capture-prompt.ts`) · Polaris MCP server (`client.ts`, stdio) · Polaris daemon (`daemon.ts`, `127.0.0.1:4322`) · `~/.polaris/` (config + JSONL logs) · `~/.claude/settings.json` (hook/MCP wiring) |
20
+ | **Cloud (single Hetzner VPS today, behind Caddy/TLS)** | API service (`server.ts`, `:4321`) · Web app/dashboard (`app.ts`, Hono, `:3000`) · Slack bridge (`bridge.ts`, one process per org) · Postgres 17 |
21
+ | **External SaaS** | Google (OAuth/SSO) · Slack (OAuth + Socket Mode + Web API) |
22
+ | **NOT integrated** | claude.ai · Claude Code on the web · Claude Code mobile. Polaris only touches the *local* Claude Code CLI. |
23
+
24
+ ---
25
+
26
+ ## 3. The big picture
27
+
28
+ ```
29
+ DEVELOPER MACHINE (one per teammate)
30
+ ┌───────────────────────────────────────────────────────────────────────────────┐
31
+ │ │
32
+ │ ┌───────────────┐ stdio JSON-RPC ┌──────────────────┐ │
33
+ │ │ Claude Code │◀─────────────────▶│ Polaris MCP srv │ (client.ts, stdio) │
34
+ │ │ CLI │ │ tools: │ │
35
+ │ │ (the agent) │ │ connect/reply/ │ │
36
+ │ │ │ │ status/context/ │ │
37
+ │ │ │ │ rename/disconn. │ │
38
+ │ └──┬─────▲──────┘ └─────────┬────────┘ │
39
+ │ │ │ additionalContext │ HTTP ◀──▶ │
40
+ │ │ fires hooks (on events) │ (connect/reply/status/context) │
41
+ │ ▼ │ ▼ │
42
+ │ ┌─────────────────────────┐ HTTP POST ┌───────────────────────────────┐ │
43
+ │ │ HOOKS │─────────────▶│ Polaris DAEMON 127.0.0.1:4322│ │
44
+ │ │ • capture.sh Pre/Post │ /events │ • session mappings (mem) │ │
45
+ │ │ ToolUse ──▶ (1-way)│ │ • injectQueues (mem) │ │
46
+ │ │ • capture-stop.ts Stop │─────────────▶│ • ~/.polaris/logs/*.jsonl │ │
47
+ │ │ ──▶ (1-way, transcript)│ /events │ • token from ~/.polaris/cfg │ │
48
+ │ │ • capture-prompt.ts │◀────────────▶│ │ │
49
+ │ │ UserPromptSubmit │ /events resp │ │ │
50
+ │ │ ◀── pendingInjects │ pendingInject│ │ │
51
+ │ └─────────────────────────┘ └──────────┬──────────▲──────────┘ │
52
+ │ │ │ │
53
+ └────────────────────────────────────────────────────── │ ──────── │ ──────────────┘
54
+ HTTP POST /events│ │ WebSocket (per session)
55
+ (event ingest, │ │ cloud──▶daemon push
56
+ 1-way) ▼ │ (broadcast incl. injects)
57
+ ┌───────────────────────────────────────────────┐
58
+ │ CLOUD API SERVICE :4321 │
59
+ │ (server.ts) │
60
+ Google ◀──▶ ┌────────────┐ │ REST (auth-gated via authOrgId / Bearer JWT): │
61
+ OAuth │ WEB APP │ HTTP │ projects·sessions·events(ingest/GET page)· │
62
+ Slack ◀──▶ │ :3000 │◀────────▶│ inject·handoff·driver·label·search·messages │
63
+ OAuth │ (Hono) │ fetch │ WS /projects/:p[/sessions/:s]/ws ──▶ push │
64
+ │ dashboard, │ +Bearer │ SSE /projects/:p[/sessions/:s]/events ──▶ push │
65
+ │ transcript,│ │ in-mem subscriber maps (projectSubs/SSE) │
66
+ │ search, │ └───────────────┬───────────────────────────────┘
67
+ │ inject │ │ read/write
68
+ │ proxy │ ▼
69
+ └─────┬──────┘ ┌─────────────────────────────┐
70
+ │ read/write │ POSTGRES 17 │
71
+ └────────────────────▶│ orgs·users·projects· │
72
+ ▲ │ sessions(+label)·events │
73
+ │ read/write│ (JSONB)·schema_migrations │
74
+ ┌─────────────┴──────┐ └─────────────────────────────┘
75
+ Slack ◀──────▶│ SLACK BRIDGE │ poll events every 5s ──▶ post to Slack
76
+ (Socket Mode + │ (one per org) │ inbound Slack msg ──▶ writes inject to DB
77
+ Web API) │ bridge.ts │ ≈≈▶ does NOT broadcast to API (Slack→live
78
+ └────────────────────┘ session inject deferred: needs LISTEN/NOTIFY)
79
+ ```
80
+
81
+ ---
82
+
83
+ ## 4. Communication paths (precise)
84
+
85
+ | From → To | Transport | Direction | Purpose | Auth |
86
+ |---|---|---|---|---|
87
+ | Claude Code ↔ MCP server | stdio JSON-RPC | two-way | tool calls (`polaris_connect/reply/status/context/rename`) | none (local, trusted) |
88
+ | Claude Code → hooks | process exec + stdin | one-way trigger | fire on Pre/PostToolUse, Stop, UserPromptSubmit | none (local) |
89
+ | Claude Code ◀ capture-prompt hook | hook stdout `additionalContext` | one-way **into the model** | deliver queued teammate injects on next prompt | none (local) |
90
+ | `capture.sh` / `capture-stop.ts` → daemon | HTTP POST `/events` | one-way (fire-and-forget) | event capture | none (local) |
91
+ | `capture-prompt.ts` ↔ daemon | HTTP POST `/events` | two-way (req/resp) | capture **and** read back `pendingInjects` | none (local) |
92
+ | MCP server ↔ daemon | HTTP (`/register`,`/connect`,`/reply`,`/status`,`/context`,`/rename`) | two-way | session control & queries | none (local) ⚠ |
93
+ | daemon → API | HTTP POST `/projects/:p/sessions/:s/events` | one-way | relay captured events upstream | Bearer JWT (or anon→default in dev) |
94
+ | daemon ◀ API | **WebSocket** per session `/projects/:p/sessions/:s/ws` | one-way push (cloud→daemon) | receive broadcasts, esp. `inject` events → `injectQueues` | token in WS connect |
95
+ | Web app ↔ API | HTTP `fetch` + `Authorization: Bearer` | two-way | transcript events, search, inject proxy | Bearer JWT |
96
+ | Web app ↔ Google | OAuth 2.0 redirect + userinfo | two-way | SSO login/signup | OAuth |
97
+ | Web app ↔ Slack | OAuth (`/slack/install`,`/slack/callback`) | two-way | obtain bot token | OAuth |
98
+ | Web app → CLI (login) | redirect to `http://127.0.0.1:<port>/callback?token=` | one-way | deliver JWT to the CLI loopback server | ⚠ no state/nonce |
99
+ | Browser ↔ Web app | HTTP, **JWT in `?token=` query param** | two-way | dashboard/transcript/search pages | JWT in URL ⚠ |
100
+ | Bridge ↔ Slack | Socket Mode **WebSocket** (appToken) + Web API (bot token) | two-way | receive Slack msgs (ack) + post events | Slack app/bot tokens |
101
+ | Bridge ↔ Postgres | SQL | two-way | poll `getOrgEventsSince` (every 5s); write inbound injects via `pushEvent` | DB creds |
102
+ | Bridge → API | — | **none today** | (Slack-originated inject → live session) | ≈≈▶ deferred (LISTEN/NOTIFY) |
103
+ | API ↔ Postgres | SQL (`postgres` client) | two-way | system of record | DB creds |
104
+ | CLI → API/web | HTTP | two-way | validate token, post device-connected event | Bearer |
105
+ | CLI → `~/.claude` + `claude mcp add` | filesystem + subprocess | one-way | install hooks, statusline, SKILL.md, register MCP | local |
106
+
107
+ ---
108
+
109
+ ## 5. Key end-to-end flows
110
+
111
+ ### 5a. Capture (agent activity → record → teammates) — works today
112
+ 1. Driver prompts Claude Code → `UserPromptSubmit` fires `capture-prompt.ts`; agent finishes → `Stop` fires `capture-stop.ts` (walks the transcript for the full response); tool calls fire `capture.sh`.
113
+ 2. Hook → daemon `POST /events`. Daemon stamps sender (`user:*` for prompts, `agent:*` for Stop/tool events), logs to `~/.polaris/logs/*.jsonl`, relays upstream to the API.
114
+ 3. API `pushEvent` → Postgres `events` (JSONB), then `broadcastEvent` (WS) + `broadcastSse` (SSE) to subscribers.
115
+ 4. Subscribers: other daemons' session WS, the dashboard (SSE), and — via the bridge's 5s DB poll — Slack channels.
116
+
117
+ ### 5b. Inject / live steering (teammate → driver's live agent) — works today (dashboard-initiated)
118
+ 1. Advisor opens the session transcript in the dashboard and submits guidance → web `POST /sessions/:p/:s/inject` proxies to API `POST /projects/:p/sessions/:s/inject` (sender derived from the advisor's JWT, not client input).
119
+ 2. API persists the `inject` event + `broadcastEvent` over the session WS.
120
+ 3. The driver's daemon (subscribed to that session's WS) receives it → pushes to `injectQueues[ccSessionId]`.
121
+ 4. Driver's next prompt → `capture-prompt.ts` → daemon `/events` response returns `pendingInjects` → hook emits `hookSpecificOutput.additionalContext` → **Claude Code folds the teammate's guidance into the model's context.** Delivery is at the next turn boundary (not mid-tool-call).
122
+ 5. ≈≈▶ **Slack-initiated** inject is deferred: the bridge writes the inject to the DB but does not broadcast, so it never reaches the live session. Fix = Postgres `LISTEN/NOTIFY` (bridge `NOTIFY` on insert → API `LISTEN` → broadcast).
123
+
124
+ ### 5c. Read / search (knowledge retrieval) — works today
125
+ - Transcript: web `GET /sessions/:p/:s` → API `GET /projects/:p/sessions/:s/events?limit=&before=` (keyset pagination, DESC) → rendered chronologically with injects highlighted.
126
+ - Search: web `GET /search` → API `GET /search?q=&project=&sender=&source=` → `searchEvents` (query-time Postgres full-text with `ts_headline` snippets).
127
+
128
+ ### 5d. Onboarding / login
129
+ 1. `npx @lightupai/polaris` → CLI starts a loopback server, opens browser to web `/auth/cli?port=…`.
130
+ 2. Web → Google OAuth → callback mints a JWT (`createToken`, HS256) carrying `org_id` + `participant_id` (org resolved/created by email domain).
131
+ 3. Web redirects the JWT to the CLI's `127.0.0.1:<port>/callback?token=` → stored in `~/.polaris/config.json`.
132
+ 4. CLI installs hooks + statusline into `~/.claude/settings.json`, writes SKILL.md, registers the MCP server via `claude mcp add`, starts the daemon.
133
+
134
+ ---
135
+
136
+ ## 6. Persistence layer
137
+
138
+ **Postgres 17 (durable, system of record).** Migrations are now additive/idempotent (`schema_migrations` + `ALTER … IF NOT EXISTS`); the old drop-and-recreate path is removed.
139
+
140
+ | Table | Key columns | Notes |
141
+ |---|---|---|
142
+ | `orgs` | id, name, slug, domain, `slack_team_id`, **`slack_bot_token`** (⚠ plaintext), `slack_system_channel_id` | tenant root |
143
+ | `users` | id, email (unique), name, org_id→orgs, participant_id | identity |
144
+ | `projects` | id (uuid), org_id, name, slack_channel_id/name | unique (org_id, name) |
145
+ | `sessions` | PK (project_id, name), org_id, driver, **`label`** (new) | conversation/workstream |
146
+ | `events` | id (uuid), org_id, project_id, session, timestamp, source (`hook`/`inject`/`reply`), sender, **`payload` JSONB** | the record; the JSONB holds prompts/responses/tool I/O/`raw_turn` |
147
+ | `schema_migrations` | id, applied_at | new migration ledger |
148
+
149
+ Indexes: `idx_events_project(project_id, timestamp)`, `idx_events_session(project_id, session, timestamp)`. Full-text search is currently **query-time** (no FTS index yet — flagged as a pre-scale follow-up).
150
+
151
+ **Ephemeral / in-memory (lost on restart):**
152
+ - API: `projectSubs` / `sessionSubs` (WS) and SSE client sets — **per-process**, so horizontal scaling needs shared pub/sub.
153
+ - Daemon: `sessions` (mappings) and `injectQueues` — per-machine, per-process.
154
+
155
+ **Local on-disk (per developer machine):**
156
+ - `~/.polaris/config.json` — profiles + JWT (⚠ plaintext, no chmod).
157
+ - `~/.polaris/logs/*.jsonl` — full event payloads for recovery (⚠ plaintext transcripts, no rotation/redaction).
158
+
159
+ ---
160
+
161
+ ## 7. Auth & security model
162
+
163
+ **Authentication**
164
+ - Humans: Google SSO → JWT (HS256 via `jose`, issuer `polaris`, 30-day expiry). Payload carries `org_id` + `participant_id`.
165
+ - Service→service: the daemon and web app present that JWT as `Bearer` to the API.
166
+ - Slack: per-org bot token obtained via OAuth (stored in `orgs.slack_bot_token`); bridge uses a `SLACK_APP_TOKEN` for Socket Mode.
167
+
168
+ **Authorization & tenancy**
169
+ - Every DB query is **org-scoped** (`WHERE org_id = …`) → tenant isolation at the data layer.
170
+ - API gate `authOrgId`: valid Bearer → that org; **in production**, missing/invalid token → **401**; in dev/test → falls back to a shared `default` org (so the suite runs unauthenticated).
171
+ - `assertSecretConfigured()` fails startup in production if `POLARIS_JWT_SECRET` is unset or the dev default.
172
+ - Inject `sender` is derived from the authenticated token (client-supplied sender ignored) → no impersonation on the inject path.
173
+ - **No per-project ACLs** — every org member can read all projects/sessions/events (org-wide read; acceptable for internal alpha, ACLs deferred).
174
+
175
+ **Transport**
176
+ - Prod: Caddy terminates TLS for `app.` and `api.` (Let's Encrypt); daemon↔cloud WS upgrades to `wss`.
177
+ - Local: hooks ↔ daemon ↔ MCP all on `127.0.0.1` plaintext (acceptable — local trust domain).
178
+
179
+ **Known security gaps (tracked in `readiness-review-2026-06.md`)**
180
+ - ⚠ Local daemon HTTP endpoints are **unauthenticated** — any local process can post events, inject, or read context as the user.
181
+ - ⚠ JWT travels in the browser URL (`?token=`) and on the CLI loopback callback; CLI callback has **no state/nonce** (login-CSRF/token-injection risk).
182
+ - ⚠ Slack bot tokens stored **plaintext** in Postgres; daemon JSONL logs store **plaintext transcripts**; compose ships a default DB password.
183
+ - ⚠ Google `email_verified` is not checked; org auto-join by email domain has no admin approval.
184
+ - ⚠ No rate limiting on auth/token endpoints.
185
+ - Note: the Slack bridge uses Socket Mode (authenticated at the WS transport by Slack), so classic request-signature verification is N/A — but there is no **app-level authorization** on *which* Slack user may inject into *which* session.
186
+
187
+ ---
188
+
189
+ ## 8. Trust boundaries
190
+
191
+ 1. **Local machine** (Claude Code, hooks, MCP, daemon) — one trust domain; localhost, unauthenticated between local components.
192
+ 2. **Local ↔ cloud** — authenticated boundary (Bearer JWT over HTTPS/WSS). This is the real security perimeter.
193
+ 3. **Cloud internal** (API ↔ web ↔ bridge ↔ Postgres) — co-located today; only Caddy is exposed publicly. Bridge↔API is currently a *gap* (no path), not a boundary.
194
+ 4. **Cloud ↔ external SaaS** (Google, Slack) — OAuth-mediated.
195
+
196
+ ---
197
+
198
+ ## 9. What is intentionally NOT here (yet)
199
+
200
+ - **No claude.ai / Claude Code web / mobile integration.** Polaris hooks into the *local* Claude Code CLI only.
201
+ - **Slack-initiated injection into a live session** — deferred (needs `LISTEN/NOTIFY`); Slack capture→post works, dashboard-initiated inject works.
202
+ - **`claude/channel` push delivery** — deferred in favor of the ungated `UserPromptSubmit` `additionalContext` path.
203
+ - **Semantic/RAG search, per-project ACLs, multi-session hook-routing disambiguation, horizontal API scaling (shared pub/sub), multi-org Slack bridge** — all deferred follow-ups.
@@ -0,0 +1,136 @@
1
+ # Polaris — Product & Investment Critique
2
+
3
+ *Written 2026-06-09. VC/PM-style assessment of the idea, followed by an MVP critique of the codebase as of commit `d5956b5`.*
4
+
5
+ ---
6
+
7
+ # Part 1: The Investment Critique
8
+
9
+ **The pitch in one line:** Polaris captures everything happening in AI coding agent sessions (Claude Code, Cursor, etc.) and bridges it bidirectionally into Slack, so teammates can observe live agent sessions, inject advice into them, and hand off "driver" roles — a multiplayer layer for agentic coding.
10
+
11
+ **Verdict up front:** the problem space is real and the timing instinct is right, but as an investment this is a **pass at the current stage** — the core wedge was commoditized by the platform owner mid-build, the closest comparable raised only $500K and charges $9/month, and there is zero usage evidence to offset that. There is one under-developed angle (project-level context pooling and handoff) that could be a thesis, but it isn't the product's center of gravity today.
12
+
13
+ ## The bull case (and it's genuinely there)
14
+
15
+ The macro tailwind is strong. Roughly 85% of developers now use AI coding tools and ~73% use them regularly; the AI coding assistant market is estimated around $8.5–12.8B in 2026, with projections to $30B+ by 2032 at ~27% CAGR. Cursor alone hit $2B ARR; Copilot has 4.7M paid subscribers. The shift Polaris bets on — work moving from "one dev, one IDE" to "teams supervising fleets of concurrent agent sessions" — is the consensus 2026 narrative, not a fringe one.
16
+
17
+ And the architecture bet is sound: model-agnostic capture via hooks + injection via MCP means Polaris isn't married to one vendor, which matters when 70% of engineers stack 2–4 AI tools simultaneously.
18
+
19
+ ## The bear case (this is where it falls apart)
20
+
21
+ **1. Anthropic shipped the wedge as a free platform feature.** Claude Code in Slack now exists natively: mention @Claude in a thread, it spins up a session, posts progress updates back into the thread, and the team follows along; on Team/Enterprise plans, sessions are automatically visible to the whole organization. That is observability + Slack-bridging + team visibility — Polaris's headline loop — bundled into the $25/seat the customer already pays. Cognition's Devin similarly starts sessions from Slack and Linear natively, with an $80/mo Teams plan. When the platform owner ships your core feature, the startup answer must be either "we're cross-vendor and they're not" or "we go deeper than they ever will." Polaris's cross-vendor claim is currently aspirational — the hooks, skill install, and statusline are all Claude Code-specific in practice (`~/.claude/`, `hooks/capture.sh`).
22
+
23
+ **2. The closest comparables show weak willingness-to-pay.** Omnara — YC-backed, ex-Meta/Microsoft founders, "command center for your coding agents," terminal + web + mobile — raised $500K and charges **$9/month** with a free tier. Conductor, Vibe Kanban, Claude Squad, Terragon populate the same "manage parallel agent sessions" niche, mostly free or open source. A crowded field of sub-$10 tools and one tiny financing round is the market telling you this layer, as currently framed, is a feature with thin standalone economics — not a venture-scale company.
24
+
25
+ **3. The moat question has a bad answer.** The entire system is ~4,000 LOC of source. That's a compliment to the code's economy and an indictment of the defensibility: capture-via-hooks plus inject-via-MCP is reproducible by a competent team in weeks, and the underlying primitives (hooks, MCP) are public standards explicitly designed to make this easy. There's no proprietary data asset, no network effect yet (the network is your existing Slack org), and no workflow lock-in.
26
+
27
+ **4. The core behavioral assumption is unvalidated and contrarian.** Polaris bets on *synchronous, multiplayer* supervision — an advisor watching a live session and injecting guidance mid-flight. The observed market behavior is the opposite: **async delegation** (fire a task from Slack/Linear, walk away, review the PR). Every traction story in this space — Devin, Claude Code in Slack, Omnara's mobile "approve while away from your desk" — is async. There's also a real adoption headwind the docs don't address: streaming every prompt and response a developer types into a shared Slack channel reads as surveillance to many engineers and as noise to everyone else. With AI-tool *trust* at only ~29% among developers despite 84% adoption, "broadcast my entire agent transcript to the team" is a hard cultural sell, and there's no evidence in the repo (no design partner configs, no usage data, no testimonials) that any team has wanted this.
28
+
29
+ **5. No traction, no GTM motion.** No deployed instance, no users beyond the author, no pricing page, no waitlist. At pre-seed, that's forgivable only if the insight is contrarian *and* the founder shows evidence for it. Right now the evidence runs the other way (point 4).
30
+
31
+ ## What would change the verdict
32
+
33
+ The most defensible idea in PLAN.md is the one getting the least investment: **project-level context pooling and driver handoff** (PLAN.md:99–115). "All sessions in a project share a persistent, queryable context; a new driver — human or agent — is seeded with everything that happened before" is a *team memory layer for agents*. Anthropic won't build that cross-vendor; Slack-bridging doesn't capture it; and it compounds into a data asset (the org's agentic work history) that has real lock-in. A repositioning around "shared memory and handoff for agent fleets," with Slack demoted to one of several surfaces, plus 5–10 design partners actually using handoff weekly, would make this a fundable pre-seed. As "Slack bridge for Claude Code sessions," it is not.
34
+
35
+ ---
36
+
37
+ # Part 2: The MVP, Critiqued Against That Analysis
38
+
39
+ ## Current state — credit where due
40
+
41
+ This is a real, working end-to-end system, not a demo: Google SSO with org multi-tenancy, a local daemon routing multiple concurrent sessions, MCP tools for connect/reply/context/rename, bidirectional Slack bridge with identity mapping and three long-message modes, driver handoff, SSE-backed dashboard, and a one-command installer (`polaris login`). ~4,000 LOC of source with ~2,500 LOC of tests across 11 files, CI on PRs, and 43 commits in the past month. The engineering quality is above typical MVP bar.
42
+
43
+ ## The structural critique: it's built sideways, not deep
44
+
45
+ The classic MVP mistake here is **horizontal completeness before vertical validation**. The repo contains SaaS scaffolding — orgs, SSO, JWT auth, multi-tenancy, OAuth flows, a marketing landing page — but the product **cannot acquire a user**: there's no hosted instance, no Docker image, no deploy story at all (Docker Compose is dev-only Postgres). Meanwhile the single riskiest assumption — *will an advisor actually inject context into a live session, repeatedly?* — could have been tested with one hardcoded org and a bot token in a week. The company's plumbing got built before the product's proof. Compounding this: there are **no usage analytics beyond prompt counts**, so even if a design partner appeared tomorrow, activation, retention, and injection frequency couldn't be measured.
46
+
47
+ Second structural issue: the **"zero-loss capture" claim is the foundation of the whole value prop, and it leaks**. The README's own known-issues list admits that when a user rejects a tool call, no Stop event fires and the agent response is lost; that the schema auto-migration *drops all events*; and that Unicode edge cases silently fall back to dropping `raw_turn`. For a product whose entire pitch is "system of record for agent sessions," data-loss bugs are existential, not cosmetic.
48
+
49
+ Third: the **moat-relevant features are the stubbed ones**. Context pooling — the defensible part — exists as a single `polaris_context` tool fetching sibling-session events with no pagination, no summarization, no relevance filtering. Handoff "seeds the new driver with project history," but with unpaginated raw events, that breaks at exactly the scale where it becomes valuable.
50
+
51
+ ## Feature gaps, mapped from the market analysis
52
+
53
+ Ordered by how directly they address the bear case:
54
+
55
+ 1. **Async task initiation from chat** — start a session *from* Slack ("@polaris fix the login bug in #pj"), not just observe one. This is the loop Claude-in-Slack and Devin proved demand for; Polaris only has the observe/inject half. Without it, Polaris is read-mostly.
56
+ 2. **Context pooling as a real product** — summarized, queryable project memory (rollups per session, semantic retrieval, "what changed since I last drove"), not raw event dumps. This is the moat candidate.
57
+ 3. **Prove model-agnosticism** — actual Cursor/Windsurf adapters. It's the one structural advantage over Anthropic, and today it's a sentence in PLAN.md, not code.
58
+ 4. **Approval/HITL flows** — PLAN.md explicitly punts permissions and approvals "outside polaris" (PLAN.md:73). That's backwards: remote approval of risky tool calls is the #1 thing teams supervising agents pay for (it's Omnara's whole mobile pitch), and Polaris already intercepts `PreToolUse` events — the data is in hand.
59
+ 5. **Mobile/remote surface** — the demonstrated buyer behavior is "my agent is running and I'm away from my desk." Slack-on-phone partially covers this but with no approve/deny affordances.
60
+ 6. **Git/PR linkage** — sessions aren't tied to branches, commits, or PRs, so the "record" can't answer the question reviewers actually ask: *what did this session ship?*
61
+ 7. **Enterprise table stakes** — per-project ACLs (currently every org member sees everything — combined with full-transcript capture, this is a privacy objection waiting to kill a deal), audit trail, secret rotation (JWT secret defaults to `polaris-dev-secret-change-in-prod`), pagination, and a real migration framework instead of drop-and-recreate.
62
+ 8. **Session replay/review UI** — the dashboard shows live counts but there's no way to *read* a past session well, which is the obvious daily-use surface for a system of record.
63
+
64
+ ## Bottom line
65
+
66
+ As engineering, this is an unusually disciplined MVP. As a product, it's an MVP of the wrong slice: it thoroughly implements the layer that Anthropic just gave away (Slack visibility into Claude Code sessions) while leaving the differentiated layer (shared agent memory, handoff, cross-vendor capture, approval flows) as stubs and punts. The next 90 days should be: host it, get five teams on it, instrument injection/handoff usage, and move the engineering effort from Slack formatting polish to context pooling and HITL — because that's where both the moat and the willingness-to-pay live.
67
+
68
+ ---
69
+
70
+ # Update: Re-analysis at `c81e555` (2026-06-11)
71
+
72
+ *19 commits landed since the analysis above (`d5956b5..c81e555`, ~1,380 insertions). This section re-scores the critique against that delta. Verified live during this analysis: `https://app.polaris.lightup.ai` returns HTTP 200 with valid TLS; `https://api.polaris.lightup.ai/status` returns `{"ok":true}`; npm package `@lightupai/polaris` is published with auto-CD on merge (~16 versions in 3 days).*
73
+
74
+ ## What shipped
75
+
76
+ 1. **Production deployment (#45, #46, #52–54)** — Docker Compose + Caddy/Let's Encrypt on a Hetzner CX22, `deploy.sh`, `.env.example`, CD pipeline auto-publishing to npm on master merge, and all client defaults flipped to `polaris.lightup.ai`. Onboarding is now one command: `npx @lightupai/polaris`.
77
+ 2. **CLI restructure (#50, #51, #56, #57)** — install/login split (components install before OAuth, so a failed auth still leaves a working install), named profiles with `polaris use`, legacy-credentials migration, port-collision fix.
78
+ 3. **Daemon reliability (#47–49 + TODOs)** — all MCP traffic now routes through the daemon as a single nexthop; a local JSONL log records every event before forwarding; upstream API errors are logged. Notably, the whole sequence landed in a 14-minute window the same night as the prod cutover — it's a black-box recorder bought for cutover risk, not a recovery system.
79
+ 4. **Identity correctness (#60, #61)** — the daemon now stamps the true sender per hook event (`UserPromptSubmit` → `user:*`, `Stop`/`PreToolUse`/`PostToolUse` → `agent:*`); previously *every* event was attributed to the human. Auto-generated session slugs (`s-7a3f`) replace user-chosen names. Slack personas render from the real sender.
80
+ 5. **Two design docs** — `design-named-agents.md` (persistent specialist agents as teammates, agent registry, daemon-as-universal-nexthop, 4-phase context model from org-wide shared memory to subagent hierarchy) and `design-sessions.md` (sessions as disposable conversation threads; handoff as a first-class lifecycle primitive including "let the agent finish this autonomously").
81
+
82
+ ## Scorecard vs. the original critique
83
+
84
+ | Issue from original critique | Status | Evidence |
85
+ |---|---|---|
86
+ | Cannot acquire a user / no hosted instance | **Closed** | Live at app.polaris.lightup.ai; npm published; one-command onboarding |
87
+ | Zero-loss capture leaks | **Partially addressed** | Daemon JSONL log makes API-side losses *manually* recoverable; but tool-rejection loss, Unicode `raw_turn` fallback, and drop-all-events migration are all still open, and `polaris recover` is only a TODO (#58) |
88
+ | No usage analytics | **Untouched** | Zero instrumentation added — ironic now that hosting exists and design partners could actually arrive |
89
+ | Context pooling stubbed | **Untouched** | `polaris_context` still proxies raw unbounded event queries; the moat candidate got design-doc prose, no code |
90
+ | Gap 1: async task initiation from chat | **Untouched** | `bridge.ts` unchanged; spawning/auto-join explicitly deferred in the design doc |
91
+ | Gap 2: context pooling as product | **Untouched** | Phases 1–4 in design-named-agents.md are all unimplemented |
92
+ | Gap 3: prove model-agnosticism | **Untouched / worse** | No Cursor/Windsurf code; daemon now *hardcodes* `agent:claude` as default identity (daemon.ts:223) |
93
+ | Gap 4: approval/HITL flows | **Untouched** | `PreToolUse` is used only for sender stamping; neither design doc contains the word "approval" |
94
+ | Gap 5: mobile/remote surface | **Untouched** | — |
95
+ | Gap 6: Git/PR linkage | **Untouched** | Not even an open question in the new sessions design |
96
+ | Gap 7: enterprise table stakes | **Untouched** | No ACLs, no audit trail, JWT secret still defaults to dev value, queries still unpaginated, migrations still drop-and-recreate — now pointed at production |
97
+ | Gap 8: session replay UI | **Untouched** | Dashboard changes were copy edits; session search explicitly deferred |
98
+
99
+ ## New issues introduced by this delta
100
+
101
+ - **Single-tenant Slack bridge on a multi-tenant hosted instance** — `bridge-discover-org.ts` does `SELECT ... WHERE slack_team_id IS NOT NULL LIMIT 1`. The *second* org to connect Slack on the hosted instance silently gets no bridging. This caps acquisition at exactly one org — directly undermining the point of deploying.
102
+ - **Drop-and-recreate migration aimed at production with no backups** — `db.ts:71-74` still drops events/sessions/projects on schema mismatch; `deploy.sh` is `git pull && docker compose up`; the backup cron is a TODO. One schema change away from wiping all production events unrecoverably.
103
+ - **Plaintext transcript logs on every dev machine** — the new JSONL recovery log writes full prompts/responses/tool payloads to `~/.polaris/logs/` with no rotation, retention, or redaction — new data-at-rest exposure for a product already facing surveillance objections.
104
+ - **Privilege concentration in an unauthenticated daemon** — all MCP traffic now routes through localhost:4322, which holds the bearer token and has no auth on its endpoints; any local process can post events, inject replies, and read project context as the user.
105
+ - **Login CSRF + tokens in URLs** — the localhost OAuth callback has no state/nonce binding; tokens transit as query params; `~/.polaris/config.json` is plaintext with no restrictive file mode.
106
+ - **Onboarding sharp edges** — `login` hangs forever on abandoned OAuth; `install()` wholesale-replaces users' existing hook arrays and statusline; `polaris use` switches profiles but not the SKILL.md identity; `logout` leaves the dual-written legacy credentials behind.
107
+ - **No release discipline** — CD publishes on every master push including doc-only TODO commits; package.json (0.0.5) permanently diverges from npm (0.0.16); a broken release (daemon port collision) shipped to npm; no tags, changelog, or smoke test of the published artifact.
108
+ - **`polaris_reply` misattribution regression** — the daemon `/reply` handler still sends `sender: mapping.user` in a fabricated Stop payload, and the #61 persona fix now renders it as the *human*. An agent-authored floor message appears in Slack as the person. The exact bug class #60 fixed, surviving in the adjacent endpoint.
109
+
110
+ ## Does the verdict change?
111
+
112
+ **Marginally, in the right direction — but the investment verdict stands.** Mapping the delta onto the five bear-case points:
113
+
114
+ - **Bear 5 (no traction/GTM) — weakened.** This was the most forgivable point and the delta attacks it directly: the product can now actually acquire a user. But still zero users in evidence, no pricing, no waitlist, no analytics to measure traction even now, and the one-org Slack bridge caps acquisition at a single customer.
115
+ - **Bear 1 (Anthropic shipped the wedge) — slightly strengthened.** Nineteen commits of deploy plumbing, CLI ergonomics, and Slack persona polish deepen the commoditized layer, and the daemon now hardcodes `agent:claude`. Cross-vendor remains zero code.
116
+ - **Bear 3 (no moat) — slightly strengthened.** The moat candidate (context pooling) received prose, not product.
117
+ - **Bear 4 (sync-multiplayer assumption unvalidated) — directionally weakened on paper only.** The design docs show real conceptual movement toward the defensible thesis: handoff as a structural property of sessions, persistent agents with phased shared-context, daemon-as-nexthop as a credible cross-vendor substrate, Slack demoted to one rendering surface. This is the strongest signal in the delta. But every async/autonomous element is explicitly deferred, and with no instrumentation the core behavioral assumption remains untestable even though hosting now exists.
118
+ - **Bear 2 (weak willingness-to-pay comparables) — unchanged.**
119
+
120
+ **Net: distribution improved; differentiation did not.** The team built the on-ramp before the destination. The honest TODO trail (#58, #59, #62, backup cron) shows self-awareness about the operational debt, which is a good founder signal — but the next commits to watch are whether *context seeding on handoff*, a *PreToolUse approval gate*, or *chat-initiated sessions* land before more persona/formatting work does. That, plus fixing the one-org bridge (the literal cap on customer #2) and putting a backup between production data and the drop-table migration, is the 30-day bar.
121
+
122
+ ---
123
+
124
+ ## Sources
125
+
126
+ - [Claude Code in Slack docs](https://code.claude.com/docs/en/slack)
127
+ - [Claude and Slack announcement](https://claude.com/blog/claude-and-slack)
128
+ - [Claude in Slack help center](https://support.claude.com/en/articles/11506255-get-started-with-claude-in-slack)
129
+ - [Omnara YC launch](https://www.ycombinator.com/launches/OCT-omnara-the-first-command-center-for-ai-agents-terminal-web-and-mobile)
130
+ - [Omnara funding](https://startupintros.com/orgs/omnara)
131
+ - [Omnara](https://www.omnara.com/)
132
+ - [AI coding assistant statistics 2026](https://uvik.net/blog/ai-coding-assistant-statistics/)
133
+ - [Market share: Cursor vs Copilot](https://www.ideaplan.io/blog/ai-coding-assistant-market-share-2026)
134
+ - [MarketsandMarkets AI code assistants report](https://www.marketsandmarkets.com/Market-Reports/ai-code-assistants-market-53503659.html)
135
+ - [Devin docs / release notes](https://docs.devin.ai/release-notes/2026)
136
+ - [Devin](https://devin.ai/)
@@ -0,0 +1,130 @@
1
+ # Polaris — Internal-Testing & Production Readiness Review
2
+
3
+ *Written 2026-06-11. Assessed at commit `b35448e`. Method: multi-agent review across five dimensions (dogfooding readiness, security, scalability/reliability, maintainability, product/competitive), with every blocker/high finding adversarially re-verified against the code. Companion to `product-critique-2026-06.md`.*
4
+
5
+ ---
6
+
7
+ ## TL;DR
8
+
9
+ Polaris at `b35448e` is **a working capture-and-broadcast pipeline, not yet the multiplayer product it advertises.** The half that records what your agent does and posts it to Slack/dashboard largely works. The half that defines the product — teammates injecting advice into a *live* session — is **dead code**. On top of that, the API enforces no authentication and the JWT secret has a hardcoded default, so it is not safe to expose. It is **not ready for internal testing today**; the gap is roughly **1–2 focused weeks** of must-have fixes.
10
+
11
+ Verification note: of the 16 blocker/high findings re-checked against the code, **14 were confirmed and 2 were partially-confirmed** (the Slack "missing signature verification" was relabeled — Socket Mode authenticates at the transport layer, so the real issue is missing app-level authorization + prompt-injection, not signatures; and the email-domain-takeover is bounded today because Google is the only IdP and returns provider-verified emails). Nothing was refuted.
12
+
13
+ ---
14
+
15
+ ## 1. Current state, by dimension
16
+
17
+ **Dogfooding readiness — not ready.** A 4-person session would connect, see teammates' *prompts* in Slack, but never see agent *responses* (a hook-wiring regression), and could never advise a running session (injection is unimplemented). Onboarding also silently depends on `bun` and the `claude` CLI being installed.
18
+
19
+ **Security — not safe to expose.** `resolveOrgId()` (`server.ts:152`) treats the bearer token as optional and falls back to a shared `default` org, so every endpoint is reachable unauthenticated. The JWT secret defaults to `polaris-dev-secret-change-in-prod` (`auth.ts:3`) with no production check — anyone who knows it can mint a token for any `org_id`, collapsing tenant isolation. Inject/event endpoints let any caller spoof an arbitrary `sender`. The localhost daemon (`:4322`) is fully unauthenticated. Secrets and full transcripts are stored plaintext. *Good news:* DB queries are consistently org-scoped, so there is no cross-org IDOR **once** the JWT secret is fixed.
20
+
21
+ **Scalability/reliability — fine for one tiny team, not for load or a 2nd org.** Two issues are functional, not just performance: the Slack bridge writes events straight to the DB and bypasses the in-process broadcast, so injected messages never reach connected agents over WebSocket; and every history query is an unbounded full table scan returned to the client. The hosted Slack bridge serves exactly **one org** (`bridge-discover-org.ts`, `LIMIT 1`). In-memory WS/SSE subscriber maps are per-process, so you can't run more than one API replica.
22
+
23
+ **Maintainability — decent structure, dangerous gaps.** ~4,500 LOC, 11 test files / ~80 cases — but coverage concentrates on HTTP plumbing and **skips the two things that make the product work**: the daemon→MCP last hop and any auth/tenancy test. The "inject" tests pass while the feature is dead, because they assert against a WebSocket the test opens directly. Schema handling is still drop-and-recreate. The SKILL.md template is duplicated verbatim in two places in `cli.ts`.
24
+
25
+ ---
26
+
27
+ ## 2. Must-have before internal testing can start
28
+
29
+ Ordered by importance. Rough effort in brackets.
30
+
31
+ 1. **Implement inject delivery into the live session.** [days] The daemon's `mcpCallbacks` map (`daemon.ts:25`) is never populated and the MCP client's poll loop (`client.ts:244-249`) is comment-only. Add a daemon endpoint the MCP client long-polls (e.g. `GET /inbox/:ccSessionId`); push inject events into a per-session queue; have the client drain it and surface messages to the model. **Also** route Slack injects through the broadcast path (`bridge.ts:264` writes to DB only). Do **not** depend on the `claude/channel` MCP notification — it's a gated research-preview feature requiring `--dangerously-load-development-channels` for non-allowlisted servers. *Confirmed blocker — this is the entire value prop.*
32
+ 2. **Install the transcript-walking Stop hook.** [hours] `install()` wires all hooks to `capture.sh`, a raw forwarder; native Stop payloads have no `stop_response`, so `format.ts` drops them and agent responses never reach Slack. The correct producer (`capture-stop.ts`) exists but is never referenced. Point the Stop hook at it. *Confirmed — guts the experience.*
33
+ 3. **Enforce auth + ban the default JWT secret in the deployed service.** [hours] Reject unauthenticated/invalid-token requests with 401 instead of falling back to `default` org; fail startup if `POLARIS_JWT_SECRET` is unset/default. Even an internal alpha shouldn't run with a forgeable-token, open-API posture. *Confirmed blocker.*
34
+ 4. **Add an onboarding preflight.** [hours] Check `bun` and `claude` are on PATH and abort with instructions if missing; document both as prerequisites. Today a missing `claude` makes `claude mcp add` warn-and-continue, leaving a silently broken install with no `/polaris` tools.
35
+ 5. **Fix `polaris_reply` attribution.** [hours] `daemon.ts:493` sends `sender: mapping.user`, so agent floor messages render as the human in Slack — corrupting the "who said what" record the product depends on.
36
+ 6. **Fix or surface multi-session hook routing.** [days] Events are keyed by CC session id but the client mints its own UUID; with >1 connected session the daemon returns `ambiguous` and **silently discards** the event. Engineers run multiple Claude windows — this will bite within the first hour.
37
+ 7. **Back up the DB and guard the destructive migration.** [hours] `db.ts:57-76` runs `DROP TABLE events/sessions/projects` on a schema mismatch at every startup. Gate it behind an explicit flag; `pg_dump` before pointing the team at the shared instance.
38
+
39
+ ---
40
+
41
+ ## 3. Compact product & competitive critique
42
+
43
+ *(Builds on `product-critique-2026-06.md`; not repeated here.)*
44
+
45
+ **Defensible value after Claude's native shipments — thin, and unbuilt.** The observe + Slack-broadcast + advisor-inject loop is now **commoditized by the platform owner**: Claude Code in Slack does async initiation, thread context, progress, and PRs; `claude/channel` (which Polaris is literally built on) does push-into-session; and v2.1.81's permission relay already *is* remote HITL approvals. What's left that Anthropic structurally won't build: **(a) cross-vendor capture** (one floor across Cursor + Claude + Devin) and **(b) summarized, attributed, cross-session project memory that survives handoff.** Both are design-doc prose and stub code today.
46
+
47
+ **Assumptions that may not hold.** (1) That teams want *synchronous* multiplayer — an advisor watching and injecting mid-flight — when observed 2026 behavior is *async* delegation. (2) That engineers accept their full transcript broadcast to a shared Slack channel (trust in AI tools sits ~29%). Neither is validated: there is **zero usage instrumentation and zero design-partner evidence** in the repo.
48
+
49
+ **Competitive position — squeezed, no segment owned.** Below, free platform features (Claude-in-Slack, channels) and deep incumbents (Devin, $80/mo, Slack/Linear-native); above, sub-$10 parallel-agent managers (Omnara $9/mo, Conductor, Vibe Kanban). Each owns a concrete behavior (async delegation, mobile approval) Polaris doesn't.
50
+
51
+ **Odds Claude absorbs the remaining featureset — high for most of it.**
52
+ - HITL approvals: **already shipped** (channel permission relay).
53
+ - Multi-person attribution / durable project floor: **likely** — squarely in the Slack/Teams-visibility roadmap.
54
+ - Cross-session shared memory: **likely, but Claude-only** — which is precisely why the *cross-vendor* version is the wedge.
55
+ - Cross-vendor capture: **won't build** — structural conflict with being the model vendor. This is the one durable square, and it's getting no investment.
56
+
57
+ **The thesis contradicts the code.** You cannot sell "durable attributed team memory" on a store that silently drops events, serves one org, and can wipe production on a schema change. Memory-as-moat requires the memory to be complete and multi-tenant **first**.
58
+
59
+ **Recommendation:** pick ONE wedge Anthropic can't take (cross-vendor capture, or cross-vendor shared memory), ship it as product not prose, demote Slack-broadcast to a rendering surface, freeze named-agents past the metadata table, and instrument injection/handoff usage now.
60
+
61
+ ---
62
+
63
+ ## 4. Production rollout task list
64
+
65
+ **P0 — blocks launch**
66
+ - Enforce strong JWT secret at boot; fail startup on unset/default in production. *(security)*
67
+ - Make authentication mandatory on the API; 401 instead of `default`-org fallback. *(security)*
68
+ - Validate `sender` matches the authenticated caller's `participant_id` on event/inject. *(security)*
69
+ - Replace drop-and-recreate migration with additive migrations; never auto-run destructive paths in prod. *(reliability)*
70
+ - Scheduled Postgres backups + documented restore. *(ops)*
71
+ - Rate-limit auth/token endpoints before public exposure. *(security)*
72
+ - Per-org Slack bridge (remove `LIMIT 1` single-tenant ceiling). *(multi-tenancy)*
73
+
74
+ **P1 — immediately after**
75
+ - Authn/authz on the local daemon endpoints (shared local secret). *(security)*
76
+ - Daemon write-ahead buffer with async retry/backoff so hooks never block or lose events. *(reliability)*
77
+ - Single-instance lock on the bridge to stop duplicate Slack posts. *(reliability)*
78
+ - Pagination on all event queries. *(scalability)*
79
+ - Copy hook scripts to a stable path (`~/.polaris/hooks`) so npm reinstalls/nvm switches don't leave stale paths. *(reliability)*
80
+ - Bundle/transpile for end users; drop the `npx --yes bun` download-on-every-invocation path. *(ops)*
81
+ - Capture tool approvals/rejections and ask-question answers, not just prompts. *(feature)*
82
+ - Encrypt at-rest Slack bot tokens; retention/redaction policy for captured payloads. *(security)*
83
+ - Integration test exercising the real inject path (daemon → MCP client → Claude). *(quality)*
84
+
85
+ **P2 — later**
86
+ - Postgres `LISTEN/NOTIFY` to replace the 5s bridge poll. *(scalability)*
87
+ - Daemon credential reload on `polaris use`/login. *(ops)*
88
+ - Dashboard SSE client reconnect. *(reliability)*
89
+ - Project delete / session archive lifecycle. *(feature)*
90
+ - `polaris recover` reconciliation (diff JSONL log vs DB, backfill). *(reliability)*
91
+ - CD to production on merge. *(ops)*
92
+ - Sanitize `raw_turn` Unicode before JSONB insert. *(reliability)*
93
+
94
+ ---
95
+
96
+ ## 5. Debt to clear BEFORE the named-agents / context-pooling featureset
97
+
98
+ Building the roadmap on the current foundation compounds debt. Clear these first:
99
+ - **One stable session-identity scheme** shared across hook env, MCP client, and daemon — before named agents multiply concurrent sessions and turn the ambiguous-drop bug into routine loss.
100
+ - **The real daemon↔MCP IPC channel** (long-poll inbox / unix socket) — named agents depend on reliably delivering targeted messages; don't bolt them onto the dead `mcpCallbacks` map.
101
+ - **One authoritative sender-attribution path** — reply mislabels agent as human, events infer sender from `hook_event_name`; multi-agent identities make centralized attribution a prerequisite.
102
+ - **Additive migrations** — before the featureset adds agent-identity columns/tables.
103
+ - **Server-side auth + sender validation** — before more writers/identities expand the surface.
104
+ - **Pagination/streaming on event reads** — before context pooling fans out across many sessions.
105
+
106
+ ---
107
+
108
+ ## 6. Issues that emerge or worsen ONCE the planned featureset ships
109
+
110
+ **Scalability**
111
+ - Context pooling fans unbounded full-scan queries across many sibling sessions → slow, DB-heavy context fetches as history grows.
112
+ - Named agents multiply concurrent sessions per machine → amplifies the multi-session ambiguous-drop bug.
113
+ - Bridge polls the whole org every 5s and keeps an unbounded in-memory `postedEventIds` set → grows with agent/session count.
114
+ - Single bridge + single `channelCache` per org becomes a throughput bottleneck; all org traffic funnels through one Socket Mode connection.
115
+ - Per-process, unbounded WS/SSE subscriber maps → broadcast cost and memory grow with no backpressure.
116
+ - The new inject long-poll gets hit by every named agent on an interval → O(agents × poll-rate) load without coalescing.
117
+
118
+ **Security**
119
+ - Optional auth + unvalidated `sender` + named agents = impersonation gets worse; no way to tell a genuine named agent from a forged one on the floor.
120
+ - The forgeable JWT secret + context pooling = an attacker with a minted token reads pooled cross-session context across tenants.
121
+ - Unencrypted captured payloads + pooling (which intentionally surfaces one session's content to others) = a cross-session data-exposure channel without per-session ACLs.
122
+ - The unauthenticated local daemon gains more powerful verbs (targeted inject to a specific agent) → any local process can impersonate/direct a named agent.
123
+
124
+ **Maintenance**
125
+ - Sender/identity logic already duplicated across `daemon.ts`, `server.ts`, `format.ts`, `bridge.ts` → adding agent identities scatters naming rules across all four.
126
+ - SKILL.md duplicated in two `cli.ts` sites → adding agent commands means editing both; they will drift.
127
+ - Hooks resolved from the global package dir with no staleness detection → featureset releases that change hook behavior risk silently running stale hooks.
128
+ - MCP server must be restarted to pick up new tools → shipping agent tools mid-alpha forces every dogfooder to restart with no signal to do so.
129
+ - Dead/half-built scaffolding (comment-only poll loop, unpopulated `mcpCallbacks`, unused `claude/channel` declaration, dead `POLARIS_PROMPT_STYLE`) gives false confidence and the misleading "inject" tests keep masking regressions.
130
+ - `capture-stop.ts` re-reads the full transcript on every Stop → cost compounds across more agents/sessions.