# Pydantic Team — team.pydantic.work Internal dashboard + backend for the Pydantic AI team's GitHub workflow. Tracks team members, author assessments (`trusted` / `good-enough` / `suspected-slop` / `unknown`), CI check policies, starred PR-review comments, and hosts the AICA (AI Coding Agent) feature: opencode running in Cloudflare Sandbox containers dispatched per **worktree** (one `(repo, ref)` checkout, N **threads** attached). > **If you have access to a browser, use the interface at > https://team.pydantic.work — that is the primary supported surface.** > > The HTTP APIs below exist mostly for the Pydantic GitHub Companion > browser extension and internal automation. This is **not a product > for the wider world** and access is invite-only — locked to workspace > `pydantic-ai`, currently only operating on the `pydantic/pydantic-ai` > repo. ## What this is Internal team tool. Two principal surfaces: - **Dashboard + extension state** — buckets of PRs / issues, "ready" flagging based on team-defined CI policies + post-commit-thread state, vetted-author records, starred PR-review comments, and the AICA bundle (skills / commands / agents / files shipped to coding agents). - **AICA (AI Coding Agent)** — manual + auto-dispatched opencode sessions running in CF Sandbox containers. Auto-dispatch fires on `issue.opened` against allow-listed repos and on `issue.labeled` with the `pydanty:trigger` label. ## Browser UI - `/` — dashboard tabs (PRs, issues, members, vetted authors, AICA bundle, settings). - `/agent-sessions/worktrees/:wtId/threads/:thrId` — AICA session detail. Legacy `/agent-sessions/:thrId` 301-redirects here. - `/login` — GitHub OAuth or registered passkey. ## API endpoints Every route lives under `/api`. **Almost all routes require a signed-in team-member session.** Scope checks are layered on top (`team_*:read` / `:write`). The companion extension hits the same routes cross-origin with `credentials: 'include'` from its pinned extension origin (chrome-mv3 `cakfapihjknnomffeffngbjikmadpkkk`); no separate API surface for it. ### Workspace state (`WorkspaceRoom` DO, locked to workspace `pydantic-ai`) | Method | Path | Scope | | --- | --- | --- | | GET | `/workspaces/:id/snapshot` | `team_members:read` | | GET | `/workspaces/:id/realtime` (WebSocket) | `team_members:read` | | POST | `/workspaces/:id/team-members` | `team_members:write` | | DELETE | `/workspaces/:id/team-members/:login` | `team_members:write` | | POST | `/workspaces/:id/author-records` | `team_authors:write` | | DELETE | `/workspaces/:id/author-records/:login` | `team_authors:write` | | POST | `/workspaces/:id/check-policies` | `team_policies:write` | | DELETE | `/workspaces/:id/check-policies/:name` | `team_policies:write` | | POST | `/workspaces/:id/starred-comments` | `team_members:write` | | DELETE | `/workspaces/:id/starred-comments/:commentId` | `team_members:write` | | POST | `/workspaces/:id/starred-folders` | `team_members:write` | | PATCH | `/workspaces/:id/starred-folders/:name` | `team_members:write` | | DELETE | `/workspaces/:id/starred-folders/:name` | `team_members:write` | ### Dashboard reads (GitHub-backed via the user's `ghu_*` token) | Method | Path | Notes | | --- | --- | --- | | GET | `/dashboard/prs?bucket=&login?=` | PR bucket query. | | GET | `/dashboard/issues?bucket=&login?=&unassigned?=1` | Issue bucket query. | | GET | `/dashboard/vetted-author/:login` | Recent PRs + issues by `login`. | | GET | `/github/issue-meta/:owner/:repo/:number` | Minimal projection (title, state, draft, merged_at). | ### Artifacts (issue brief / PR decisions log) Keyed by `(repoOwner, repoName, kind, number)` where `kind ∈ { issue, pr }`. | Method | Path | Scope | | --- | --- | --- | | GET | `/workspaces/:id/artifacts/issue/:owner/:repo/:number` | `team_artifacts:read` | | GET | `/workspaces/:id/artifacts/pr/:owner/:repo/:number` | `team_artifacts:read` | | GET | `/workspaces/:id/artifacts/:artifactId/versions` | `team_artifacts:read` | | GET | `/workspaces/:id/artifacts/:artifactId/versions/:n` | `team_artifacts:read` | | POST | `/workspaces/:id/artifacts/:kind/:owner/:repo/:number/start-generation` | `team_artifacts:write` | | POST | `/workspaces/:id/artifacts/:kind/:owner/:repo/:number/claim` | `team_artifacts:write` | | POST | `/workspaces/:id/artifacts/:artifactId/release` | `team_artifacts:write` | | POST | `/workspaces/:id/artifacts/:artifactId/edit` | `team_artifacts:write` | ### AICA bundle (D1 + R2 `TEAM_BUNDLES`) `/aica/bundle/files`, `/aica/bundle/skills`, `/aica/bundle/commands`, `/aica/bundle/agents` — GET / POST / PUT / DELETE pattern. GET requires `team_bundle:read`; mutations require `team_bundle:write`. ### AICA dispatch + worktrees | Method | Path | Notes | | --- | --- | --- | | GET | `/agent-sessions?userId=me\|all\|&worktreeId?=` | List threads. | | POST | `/agent-sessions` | Dispatch fresh worktree + thread. Body carries a `DispatchEnvelope` whose `extraEnv` is injected into the sandbox container — see notes. | | GET | `/agent-sessions/:id` | Snapshot. | | GET | `/agent-sessions/:id/realtime` (WS, optional `?role=viewer`) | Live AICA events. | | GET | `/agent-sessions/:id/relay` (WS) | **Token-authed only** — for the in-container relay sidecar. | | GET | `/worktrees/:wtId/relay` (WS) | Same, worktree-scoped. | | POST | `/agent-sessions/:id/fork` | Fork a thread at an item id. | | POST | `/agent-sessions/:id/archive` | Archive (frees registry slot). | | POST | `/agent-sessions/:id/apply-draft/:itemId` | Post a draft comment GitHub artifact. | | GET | `/worktrees(?creatorId=me\|all\|)` | List worktrees. | | GET | `/worktrees/:wtId` | Worktree snapshot. | | POST | `/worktrees` | Create worktree (no thread). | | POST | `/worktrees/:wtId/threads` | Attach a new thread to an existing worktree. | | POST | `/worktrees/:wtId/clone` | Fork from latest backup. | | POST | `/worktrees/:wtId/visibility` | Toggle `personal` ↔ `team`. | | POST | `/worktrees/:wtId/archive` | Archive worktree. | ### Admin `/auto-trigger-repos`, `/team-shared-creds`, `/webhook-deliveries` — all gated by `team_shared_creds:read` / `:write`. The `/team-shared-creds` payload is AES-GCM-encrypted before storage; plaintext keys never come back on GET (only `hasKey: boolean`). ### Provider health probe | Method | Path | Notes | | --- | --- | --- | | POST | `/providers/health-check` | Body `{ kind, apiKey, baseURL? }`. Probes the provider's `/models` endpoint. Used to validate a typed key before save. **Not persisted.** | ### Public webhook | Method | Path | Notes | | --- | --- | --- | | POST | `/webhooks/github` | HMAC-SHA256 against `GITHUB_WEBHOOK_SECRET`. Receives `issue.opened` + `issue.labeled` (`pydanty:trigger`) and dispatches the auto-triage chain when the repo is on the allowlist. | ## Auth & sign-up - GitHub OAuth (or registered passkey) via `auth.pydantic.work`. - **Invite-only.** No self-join allowlist entry. To get access, an existing team admin must invite you to the `team` org. - API-key access is technically possible with a `psnip_…` key holding the relevant scopes but is not advertised — this is a team-internal surface. ## Notes for agents - **Locked to one workspace, one repo.** All `:id` workspace params must be `pydantic-ai`. AICA only operates on `pydantic/pydantic-ai` today. - **`DispatchEnvelope.extraEnv` is injected verbatim** into the opencode sandbox container at dispatch. Treat it as BYOK — keys are never persisted server-side. The container also has a GitHub App installation token in `GH_TOKEN` / `GITHUB_TOKEN` for `gh` CLI use. - **Auto-dispatched threads** carry `userId = 'aica:auto'` and pull provider credentials from the admin-managed encrypted blob (or a legacy worker secret) — see `team_shared_creds:write` admin route. - **Personal-visibility threads are private to the creator.** Other team members can attach as viewers only if the thread is `visibility = team`. - **Discover your scopes.** `GET /api/auth/session` (or `https://auth.pydantic.work/api/session` directly) returns `apps[]` and `scopes[]`.