antpath 0.1.1 → 0.2.0

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.
package/docs/events.md ADDED
@@ -0,0 +1,129 @@
1
+ ---
2
+ title: Events
3
+ ---
4
+
5
+ # Events
6
+
7
+ Claude Managed Agents sessions run autonomously on Anthropic's infrastructure.
8
+ They are **non-blocking**: the SDK opens a long-lived SSE stream while the
9
+ agent thinks, calls tools, and emits messages on its own schedule. The SDK
10
+ cannot intercept a tool call to return a value — `permission_policy` is
11
+ `always_allow`. This guide covers the observe-only event surface.
12
+
13
+ ## Two ways to consume events
14
+
15
+ ```ts
16
+ // Push: register a callback in RunOptions.
17
+ const handle = await client.run(template, {
18
+ onEvent: async (event) => {
19
+ if (event.type === "provider.event") {
20
+ // event.event is a ProviderEvent (typed via a type guard below).
21
+ }
22
+ }
23
+ });
24
+ await handle.wait();
25
+ ```
26
+
27
+ ```ts
28
+ // Pull: iterate the stream concurrently with handle.wait().
29
+ const handle = await client.run(template);
30
+ const collect = (async () => {
31
+ for await (const event of handle.streamEvents()) {
32
+ // ...
33
+ }
34
+ })();
35
+ const result = await handle.wait();
36
+ await collect;
37
+ ```
38
+
39
+ Both surfaces observe the same events. They can be used together; every
40
+ subscriber sees every event. A subscriber attached after `client.run()`
41
+ returns replays the events it missed (including the initial
42
+ `sdk.status` emitted while provider resources were being created), then
43
+ continues live.
44
+
45
+ ## Event shape
46
+
47
+ ```ts
48
+ type RunEvent =
49
+ | { type: "sdk.status"; status: RunStatus; at: string }
50
+ | { type: "sdk.message_sent"; index: number; at: string }
51
+ | { type: "sdk.cleanup"; ... } // see "Cleanup events" below
52
+ | { type: "provider.event"; event: ProviderEvent; at: string };
53
+
54
+ interface ProviderEvent {
55
+ type: string; // documented Claude event type, e.g. "agent.tool_use"
56
+ payload: unknown; // raw provider payload, redacted
57
+ receivedAt: string;
58
+ }
59
+ ```
60
+
61
+ All events pass through the SDK's secret redaction before reaching any
62
+ subscriber, so payloads do not contain the Anthropic key or MCP credentials
63
+ that were supplied to `client.run()`.
64
+
65
+ ## Drain-before-`wait()` guarantee
66
+
67
+ By the time `await handle.wait()` resolves, every `onEvent` invocation
68
+ triggered by events emitted before the terminal `sdk.status` has itself
69
+ resolved or rejected. Async handlers are invoked serially in event order on
70
+ a single per-listener promise chain, so callers can persist events in order
71
+ without their own queue.
72
+
73
+ The same drain applies to handlers that throw — both modes (warn-only and
74
+ abort-on-error, see below) wait for the offending invocation to settle
75
+ before resolving.
76
+
77
+ ## Error semantics
78
+
79
+ Default: a handler that throws (or returns a rejecting promise) is logged
80
+ via the SDK's configured `logger` at `warn`. The run continues, and the
81
+ final result is unaffected.
82
+
83
+ Opt-in: `onEventAbortOnError: true` upgrades the **first** pre-terminal
84
+ handler error into a `RunStateError` that fails the run. Once terminal
85
+ status has been reached, listener errors are always logged only — they
86
+ never mutate the result, and they cannot trigger recursion when the
87
+ terminal `sdk.status: failed` event itself causes another error.
88
+
89
+ `RunStateError.message` and its `cause` field pass through the same
90
+ redaction layer used for events.
91
+
92
+ ## Cleanup events
93
+
94
+ `sdk.cleanup` events are part of the `RunEvent` union, but they are
95
+ emitted by `handle.cleanup()` after `handle.wait()` has already resolved.
96
+ By that time the event bus has been closed; subscribers attached during
97
+ the run do **not** see them. Inspect cleanup outcomes via the
98
+ `CleanupResult.operations` array returned by `cleanup()` instead.
99
+
100
+ ## Typed helpers
101
+
102
+ `packages/sdk/src/providers/known-events.ts` exports conservative type
103
+ guards that narrow `ProviderEvent.type` to documented Claude event strings.
104
+ They do **not** assert payload field shapes — the raw provider payload
105
+ stays `unknown` until callers parse it themselves.
106
+
107
+ ```ts
108
+ import {
109
+ isAgentMessage,
110
+ isAgentToolUse,
111
+ isAgentToolResult,
112
+ isAgentMcpToolUse,
113
+ isAgentMcpToolResult,
114
+ isAgentCustomToolUse,
115
+ isAgentThinking,
116
+ isUserMessage,
117
+ isSessionStatusRunning,
118
+ isSessionStatusIdle,
119
+ isSessionStatusTerminated,
120
+ isSessionError,
121
+ isAgentEvent,
122
+ isUserEvent,
123
+ isSessionEvent,
124
+ isSpanEvent
125
+ } from "antpath";
126
+ ```
127
+
128
+ The official list of event types is maintained at
129
+ [`platform.claude.com/docs/en/managed-agents/events-and-streaming`](https://platform.claude.com/docs/en/managed-agents/events-and-streaming).
package/docs/skills.md CHANGED
@@ -14,3 +14,5 @@ MVP skill inputs:
14
14
  Local directories are packaged as zip files and mounted under `/antpath/skills/` unless overridden.
15
15
 
16
16
  Inline skills are uploaded as markdown files and mounted under `/antpath/skills/` unless overridden.
17
+
18
+ The platform also mounts the `antpath` CLI at `/antpath/antpath` and a per-run manifest at `/antpath/index.json` on **every** run. Skills can invoke the managed HTTP proxy via `node /antpath/antpath proxy …` — see `credentials.md` for the policy/auth model.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "antpath",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "TypeScript SDK for running autonomous Claude Managed Agents sessions.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -20,9 +20,9 @@ This supersedes the earlier SDK-only MVP boundary.
20
20
  - Minimal tenant-scoped dashboard metadata for monitoring run execution.
21
21
  - Durable worker recovery after restarts, deploys, crashes, or missed notifications.
22
22
  - Horizontal worker scaling without duplicate run ownership.
23
- - Provider cleanup by default after terminal states, with optional retention when configured per-request or per-deployment.
24
- - BYO provider key custody with encrypted storage.
25
- - Private output capture with quota enforcement.
23
+ - Provider cleanup by default after terminal states, with optional retention of Claude-side resources when configured per-request or per-deployment.
24
+ - Per-session BYO provider key custody: keys arrive inline with each run, are encrypted in Supabase Vault for the lifetime of that single run, and are deleted at cleanup. Persistent workspace-level provider key custody is backlog.
25
+ - Unconditional capture of every session-scoped Claude artifact into private Supabase Storage, bounded only by the workspace storage cap.
26
26
  - Programmatic SDK access for submitting and observing platform runs.
27
27
  - Test-driven implementation across SDK, dashboard, worker, database, storage, and provider adapters.
28
28
 
@@ -34,6 +34,8 @@ This supersedes the earlier SDK-only MVP boundary.
34
34
  - No direct browser access to provider APIs.
35
35
  - No direct browser Supabase data access.
36
36
  - No raw prompt, raw model output, raw provider event payload, MCP credential, provider key, or output file content in normal application tables.
37
+ - No persistent workspace-level provider connections (no "saved Anthropic keys"). Every run carries its own secrets bundle; nothing about the user's Anthropic key persists past run cleanup. Persistent BYO custody is backlog.
38
+ - No user-configurable output capture: every artifact Claude exposes on the run's session is captured. There are no globs, no `capture: false`, no per-file or per-run user-visible caps.
37
39
  - No provider Agent/Environment caching in MVP.
38
40
  - No provider webhooks in MVP.
39
41
  - No Supabase Realtime in MVP.
@@ -44,7 +46,7 @@ This supersedes the earlier SDK-only MVP boundary.
44
46
  | Area | Decision |
45
47
  | --- | --- |
46
48
  | Product surface | Platform plus SDK. |
47
- | Repository | Convert the repository to an npm TypeScript workspace. |
49
+ | Repository | Convert the repository to a pnpm TypeScript workspace. |
48
50
  | SDK location | Move the existing SDK package to `packages/sdk`. |
49
51
  | SDK role | The SDK submits runs and observes status, metadata, outputs, cancellation, and deletion through the platform API. |
50
52
  | Dashboard | Build a first-party minimal dashboard. |
@@ -59,8 +61,8 @@ This supersedes the earlier SDK-only MVP boundary.
59
61
  | Service credentials | Supabase service-role credentials are server/worker only. |
60
62
  | SDK auth | SDK uses hashed, workspace-scoped API tokens. Dashboard uses Auth.js sessions. |
61
63
  | API token attribution | Token-authenticated runs are attributed to the token creator at submission time unless a future service-account model is introduced. |
62
- | Provider key custody | Workspace BYO Anthropic key is stored encrypted through Supabase Vault. |
63
- | Secret lifetime | Worker resolves provider keys per claimed lifecycle step, keeps them only in memory for that step, and drops them before lease release. |
64
+ | Provider key custody | BYO Anthropic key arrives inline with every run submission. The BFF stores the secrets bundle in Supabase Vault, attaches the secret id to the run row, and deletes the Vault entry as part of cleanup. Persistent workspace-level provider connections are backlog. |
65
+ | Secret lifetime | Worker resolves the per-run secrets bundle once per claimed lifecycle step, keeps the decoded payload only in memory for that step, and drops it before lease release. The Vault entry itself is destroyed when the run reaches terminal cleanup. |
64
66
  | Provider resources | Create provider Agent, Environment, Vault/Credential, Session, and file resources per run in MVP. |
65
67
  | Provider resource caching | Backlog; later cache Agent/Environment by Template/config hash. |
66
68
  | Worker wakeup | Poll due rows from the runs table. Postgres `NOTIFY` is a latency optimization only. |
@@ -72,12 +74,13 @@ This supersedes the earlier SDK-only MVP boundary.
72
74
  | MCP approvals | Disallow approval-required tools. Worker must not approve tools or return custom tool results. |
73
75
  | Template boundary | Templates are code-first, secret-free snapshots with stable hashes. |
74
76
  | Idempotency | Run submission idempotency is scoped by `(workspace_id, idempotency_key)` and request hash. Same key and same hash returns the existing run; same key and different hash returns conflict. |
75
- | Quotas | Enforce workspace plan quotas with per-user attribution. |
76
- | Run caps | Plan-based caps cover duration, concurrency, storage, polling, retries, and output capture. |
77
+ | Quotas | Enforce workspace storage and concurrency quotas. Storage is the only user-visible cap on output capture. |
78
+ | Run caps | Plan-based caps cover duration, concurrency, polling, retries, and workspace storage. There are no user-visible per-file or per-run output caps. |
77
79
  | Operational config | Exact cap/tier values are environment-configurable defaults with conservative fallbacks and run-level snapshots. |
78
- | Output capture | Enabled by default when configured by the run/template and within quota. |
80
+ | Boot-time env validation | Worker and dashboard validate required environment variables at startup through a shared role-scoped helper and exit/abort with an aggregated error when any are missing. No silent defaults for identity, credentials, or required URLs. |
81
+ | Output capture | Unconditional. Every file Claude exposes on the run's session is captured into private Supabase Storage. The workspace storage cap is the only user-visible quota; capture failures (quota exceeded, download failed) are persisted as `output_capture_failures` rows and surfaced in the dashboard alongside successful outputs. |
79
82
  | Output access | BFF returns signed links only after workspace authorization. |
80
- | Cleanup | Clean up Claude provider resources by default after terminal state and output capture; explicit run policy or worker default can retain them for inspection. |
83
+ | Cleanup | Clean up Claude provider resources by default after terminal state and output capture; explicit run policy or worker default can retain Claude-side resources for inspection. The per-run Vault secret is always deleted at cleanup regardless of the Claude-side retention knob. |
81
84
  | Delete semantics | User delete is soft/pending while execution, cleanup, or storage deletion is active. Hard purge happens only after cleanup/storage deletion succeeds. |
82
85
  | Retention | Run metadata and stored outputs remain until user deletion in MVP. |
83
86
  | Realtime | Defer from MVP; use BFF-mediated refresh/polling first. Revisit with custom Supabase JWT/RLS or a server-mediated realtime bridge. |
@@ -125,35 +128,44 @@ packages/
125
128
 
126
129
  ### Identity and tenancy
127
130
 
128
- - `users`
129
- - Auth.js user identity mirror or Auth.js adapter user table reference.
131
+ - Auth.js adapter tables: `users`, `accounts`, `sessions`, and `verification_token`
132
+ - Auth.js owns OAuth account linking and email magic-link verification persistence in the same Supabase Postgres database.
133
+ - Active application sessions use Auth.js JWT strategy; the `sessions` table exists for adapter compatibility but is not the active session store.
134
+ - Auth.js OAuth tokens remain confined to Auth.js adapter tables and never become antpath provider credentials.
135
+ - `app_users`
136
+ - Antpath-owned user identity row linked to Auth.js `users.id` through `auth_user_id`.
137
+ - Workspace membership, run attribution, API-token ownership, disable state, and JWT token-version checks use `app_users.id`.
138
+ - `disabled_at` and `token_version` are checked by privileged BFF routes to reject disabled or stale JWT users.
130
139
  - No provider secrets.
131
140
  - `workspaces`
132
141
  - Tenant boundary.
133
142
  - Plan, quota, status, and retention settings.
134
143
  - `workspace_memberships`
135
- - Workspace, user, role, and membership status.
144
+ - Workspace, `app_user_id`, role, and membership status.
136
145
  - Every dashboard/API operation must prove membership/scope.
137
146
  - `api_tokens`
138
147
  - Workspace-scoped SDK credentials.
139
- - Store hashed token material only, plus scopes, creator, last-used timestamp, and revoked timestamp.
148
+ - Store hashed token material only, plus scopes, `creator_app_user_id`, last-used timestamp, and revoked timestamp.
140
149
  - API-token submissions freeze the attributed user on the run row at creation time.
141
150
 
142
- ### Provider connections
151
+ ### Per-run secrets
143
152
 
144
- - `provider_connections`
145
- - Workspace-owned provider configuration.
146
- - Provider type, display name, validation status, rotation/revocation status, and encrypted secret reference.
147
- - Secret values live in Supabase Vault.
148
- - Only trusted server/worker code can resolve decrypted provider keys.
153
+ There is no `provider_connections` table in MVP. Every run carries its own secrets.
154
+
155
+ - The submission contract has a top-level `secrets` block: `{ anthropic: { apiKey }, mcpServers?: [{ name, url, headers? }], skills?: [...] }`. The shared parser enforces that **no other field** carries secret-bearing keys; the `secrets` block is the single allowlisted carrier and never serializes into normal metadata, logs, events, audit rows, or stored snapshots.
156
+ - The dashboard BFF stores the JSON-encoded bundle in Supabase Vault at submission time and writes the resulting `vault_secret_id` into `runs.execution_secret_id`. The bundle is the only durable trace of the user's Anthropic key, MCP credentials, and skill references for that run.
157
+ - Worker code reads `runs.execution_secret_id` once per claimed step, decodes the bundle in memory, builds the Claude provider, and drops the decoded values before lease release.
158
+ - At terminal cleanup the worker deletes the Vault entry and clears `runs.execution_secret_id`. Cleanup of the Vault secret happens regardless of the Claude-side retention knob.
159
+ - If the user revokes (rotates) the Anthropic key on Anthropic's side mid-run, antpath has no knowledge of this until the next provider call fails. Active runs surface a `tenant_permanent` failure with a redacted reason; cleanup proceeds with the previously vaulted key while it remains valid and gives up gracefully if it does not.
149
160
 
150
161
  ### Runs
151
162
 
152
163
  - `runs`
153
164
  - User-visible logical run.
154
- - Workspace, creator/attributed user, template snapshot/hash, status, lifecycle phase, plan caps, timestamps.
165
+ - Workspace, `creator_app_user_id`, template snapshot/hash, status, lifecycle phase, plan caps, timestamps.
155
166
  - Submission idempotency key and request hash.
156
167
  - Unique constraint: `(workspace_id, idempotency_key)`.
168
+ - `execution_secret_id` — Supabase Vault secret id for the per-run secrets bundle (Anthropic key + MCP credentials + skill references). Cleared at cleanup.
157
169
  - Lease fields: `lease_owner`, `lease_token`, `lease_expires_at`, `attempt_count`, `next_check_at`, `priority`.
158
170
  - Cancellation/deletion fields: `cancel_requested_at`, `pending_delete_at`, `deleted_at`.
159
171
  - Provider observation cursor/watermark where available.
@@ -174,24 +186,34 @@ packages/
174
186
 
175
187
  - `output_objects`
176
188
  - Workspace/run owner, storage bucket/path, size, checksum if available, content type, provider file id.
189
+ - One row per artifact successfully captured from the run's session into private Supabase Storage.
177
190
  - Used for quota accounting and signed-link generation.
191
+ - `output_capture_failures`
192
+ - Workspace/run owner, provider file id, filename, byte size as reported by the provider, reason (`workspace_quota_exceeded`, `download_failed`, etc.), redacted error message.
193
+ - Surfaced in the dashboard alongside `output_objects` so the user can see exactly which session artifacts didn't make it to storage and why.
178
194
  - `cleanup_attempts`
179
195
  - Per-resource cleanup action, status, retry count, redacted error code/message, timestamps.
180
196
  - `usage_ledger`
181
197
  - Token/cost/storage attribution by workspace and attributed user.
182
198
  - Written transactionally with source event/output rows to avoid drift.
199
+ - `audit_logs`
200
+ - Safe security audit trail for sign-in user upsert, API-token changes, run cancellation/deletion, signed-link creation, and cleanup failures.
201
+ - Stores safe identifiers, action names, redacted errors, and non-secret metadata only.
202
+ - `rate_limit_buckets`
203
+ - Durable token-bucket-style counters for abuse-sensitive BFF/API operations.
204
+ - Used before side effects for run submission, run cancellation/deletion, signed-link creation, API-token creation/revocation, and email-auth throttling where practical.
183
205
 
184
206
  ## Run lifecycle
185
207
 
186
- 1. SDK/dashboard submits a run to the BFF.
208
+ 1. SDK/dashboard submits a run to the BFF. The submission carries a top-level `secrets` bundle (Anthropic key, optional MCP credentials, optional skill references) plus the non-secret template/variables/cleanup payload.
187
209
  2. BFF validates Auth.js session or SDK API token.
188
210
  3. BFF resolves active workspace membership/scope.
189
- 4. BFF validates Template/request shape, plan limits, and idempotency key/request hash.
190
- 5. BFF writes a `runs` row with execution-affecting cap values snapshotted from plan/env defaults.
211
+ 4. BFF validates Template/request shape, plan limits, and idempotency key/request hash. The shared parser rejects every secret-bearing key outside the allowlisted `secrets` block.
212
+ 5. BFF stores the JSON-encoded `secrets` bundle in Supabase Vault and writes a `runs` row with execution-affecting cap values snapshotted from plan/env defaults plus `execution_secret_id` pointing at the Vault entry.
191
213
  6. BFF emits Postgres `NOTIFY` for fast wakeup.
192
214
  7. Worker claims due runs with row locking, `SKIP LOCKED`, lease token, and lease expiry.
193
- 8. Worker resolves the provider key from Supabase Vault for the claimed step.
194
- 9. Worker validates platform constraints: no approval-required tools, no custom antpath-executed tools, known output policy.
215
+ 8. Worker resolves the per-run secrets bundle from Supabase Vault for the claimed step.
216
+ 9. Worker validates platform constraints: no approval-required tools, no custom antpath-executed tools.
195
217
  10. Worker pre-journals intended provider resources.
196
218
  11. Worker creates provider resources and records provider IDs immediately after successful calls.
197
219
  12. Worker creates provider session and sends the initial user event.
@@ -199,8 +221,8 @@ packages/
199
221
  14. Worker later reclaims the run and polls provider session status plus event list.
200
222
  15. Worker stores only metadata events and usage, deduped by provider event id.
201
223
  16. Before every side effect, worker checks lease token and cancellation/deletion requests.
202
- 17. On terminal provider state or antpath timeout, worker captures configured outputs within per-file, per-run, and workspace quota caps.
203
- 18. Worker cleans up provider resources by default, or records them as retained when the run/deployment policy asks to retain them.
224
+ 17. On terminal provider state or antpath timeout, worker lists session-scoped files via the Claude Files API and unconditionally captures every file the workspace storage quota still has room for, persisting `output_capture_failures` rows for any file that would exceed the cap.
225
+ 18. Worker cleans up Claude-side provider resources by default, or records them as retained when the run/deployment policy asks to retain them. The per-run Vault entry is deleted regardless.
204
226
  19. Worker marks the run terminal and releases the lease.
205
227
  20. Dashboard/SDK read tenant-scoped metadata and signed output links through BFF APIs.
206
228
 
@@ -259,51 +281,59 @@ BFF/server actions must:
259
281
  - return only metadata allowed by the user's role/scope;
260
282
  - keep Supabase service-role credentials out of browser bundles.
261
283
 
284
+ Auth.js and antpath authorization stay separate in the database. Auth.js owns `users`, `accounts`, `sessions`, and `verification_token`; antpath owns `app_users` and links each row to Auth.js `users.id`. JWT callbacks expose only safe identity fields such as `app_user_id` and token version. BFF routes must load the current `app_users` row and reject disabled users or stale token versions before privileged actions.
285
+
286
+ All platform and Auth.js tables have RLS enabled and `anon`/`authenticated` table privileges revoked. This is a defense-in-depth guard against accidental Supabase Data API exposure; direct `pg` repository code remains the primary BFF/worker authorization boundary for this phase.
287
+
262
288
  Supabase Realtime is backlog until antpath either mints short-lived Supabase-compatible JWTs with RLS policies or exposes a server-mediated realtime bridge.
263
289
 
264
290
  ## Secret handling
265
291
 
266
- - Provider keys are workspace BYOK and stored via Supabase Vault.
267
- - MCP credentials follow the same no-log/no-table-secret rule.
268
- - Normal app tables store only secret references and validation/rotation status.
269
- - Worker resolves decrypted provider keys only for a claimed lifecycle step.
270
- - Secret values should use explicit redacted wrappers in code so they cannot serialize into logs, metrics, errors, events, or fixtures.
292
+ - Provider keys arrive inline with each submission and are stored in Supabase Vault tied to a single run via `runs.execution_secret_id`.
293
+ - MCP credentials and any skill payloads referenced by the run live alongside the provider key inside the same per-run Vault bundle.
294
+ - Normal app tables store only the Vault secret id, not decoded provider keys, MCP credentials, or skill payloads.
295
+ - Worker resolves the decoded bundle only for a claimed lifecycle step and drops the decoded values before lease release.
296
+ - The shared submission parser keeps an allowlist of exactly one carrier (`secrets`) and rejects every other field that looks like a secret-bearing key (`apiKey`, `accessToken`, `password`, `mcpCredentials`, etc.).
297
+ - Secret values use explicit redacted wrappers in code so they cannot serialize into logs, metrics, errors, events, or fixtures.
271
298
  - Secret redaction applies to logs, errors, run metadata, provider events, tests, fixtures, and docs.
272
- - If a provider key is revoked/rotated mid-run, active runs are failed or cancelled with a tenant-permanent error unless cleanup can still authenticate.
273
- - Revoked keys do not get a hidden grace cache in MVP. If cleanup cannot authenticate after revocation, the run/resource moves to `cleanup_failed` with an actionable tenant error.
299
+ - The per-run Vault entry is deleted as part of run cleanup. Cleanup of the Vault entry happens regardless of the Claude-side retention knob.
300
+ - If a provider key is revoked or rotated on the provider side mid-run, the next provider call fails with a `tenant_permanent` error. The worker fails the affected run, attempts cleanup with whatever credentials are still valid, and surfaces a redacted reason on the dashboard.
301
+ - There is no platform-side rotation/revocation flow because there is no persistent provider connection. To "rotate" the user simply submits the next run with a different key.
274
302
 
275
303
  ## Output storage and quotas
276
304
 
305
+ Output capture is unconditional. Every artifact the user's Claude session exposes is downloaded to private Supabase Storage. There are no user-facing knobs to opt out, no globs, and no per-file or per-run user-visible caps.
306
+
277
307
  Output flow:
278
308
 
279
- 1. Worker lists provider session files.
280
- 2. Worker checks provider file metadata, output policy, and remaining quota.
281
- 3. Worker downloads selected files with streaming hard caps.
282
- 4. If provider size is unknown or exceeds caps, worker aborts capture for that object and records a quota/cap warning.
283
- 5. Worker uploads accepted files to private Supabase Storage.
284
- 6. Worker records `output_objects` rows.
285
- 7. Worker cleans up provider resources by default, or retains them when explicitly configured.
286
- 8. Dashboard/SDK request signed links through the BFF.
309
+ 1. Worker lists files scoped to the run's Claude session: `GET /v1/files?scope_id=<session_id>`. Each entry includes `id`, `filename`, and `size_bytes`.
310
+ 2. For each file, before downloading, the worker checks `(workspace.storage_used_bytes + size_bytes)` against the workspace storage cap.
311
+ 3. If the file would exceed the cap, the worker writes an `output_capture_failures` row (`reason = "workspace_quota_exceeded"`, with filename and reported byte size) and moves on without downloading.
312
+ 4. Otherwise the worker downloads via `GET /v1/files/{file_id}/content` with a hard streaming safety cap so a malformed listing response cannot OOM the worker, uploads to private Supabase Storage at a workspace-scoped path, and writes an `output_objects` row.
313
+ 5. Other download/upload failures (network, storage, unexpected size) are recorded as `output_capture_failures` with the redacted reason.
314
+ 6. Dashboard/SDK request signed links through the BFF for any `output_objects` row.
287
315
 
288
316
  Quota rules:
289
317
 
290
- - Workspace plan quota is the hard enforcement boundary.
291
- - Per-file and per-run caps protect the worker before workspace quota is consumed.
292
- - Per-user attribution is frozen from the run row at submission time.
293
- - Free first X users is a plan/billing rule on top of workspace-level enforcement.
318
+ - Workspace storage cap is the only hard enforcement boundary. Default is generous (configurable via env, see [`environment-variables.md`](./environment-variables.md)).
319
+ - There is no per-user quota. Per-user attribution still freezes on the run row at submission time for billing/audit purposes, but it is not a quota dimension.
320
+ - There are no user-visible per-file or per-run caps. A worker-internal streaming safety cap protects against malformed listings.
321
+ - Capture failures are first-class data: the dashboard renders `<filename> (<size>) skipped: workspace quota exceeded` next to successful outputs so the user can see exactly what didn't make it.
294
322
 
295
323
  ## Cleanup and reconciliation
296
324
 
297
- Claude provider-resource cleanup is mandatory by default after terminal provider state and output capture so antpath does not leave behind provider state. Retention is opt-in and policy-driven through `cleanup.claudeSession = "retain"` or `ANTPATH_WORKER_CLAUDE_SESSION_CLEANUP_DEFAULT=retain`. Retained resources are recorded in `provider_resources.cleanup_status = retained` and remain reachable via the provider API.
325
+ Claude provider-resource cleanup is mandatory by default after terminal provider state and output capture so antpath does not leave behind provider state. Retention is opt-in for **Claude-side resources only** through `cleanup.claudeSession = "retain"` or `ANTPATH_WORKER_CLAUDE_SESSION_CLEANUP_DEFAULT=retain`. Retained resources are recorded in `provider_resources.cleanup_status = retained` and remain reachable via the provider API. Antpath-side metadata, output objects, and the per-run Vault entry are not affected by this knob — Vault entries are always deleted at cleanup; metadata and outputs persist until the user explicitly deletes the run.
298
326
 
299
327
  Explicit cleanup order:
300
328
 
301
- 1. Provider credentials/vaults.
302
- 2. Session files/session where supported.
303
- 3. Agent/archive.
304
- 4. Environment/archive or delete where allowed.
305
- 5. Uploaded provider file resources.
306
- 6. Local output metadata/storage only when user deletes a run.
329
+ 1. Capture session-scoped files into private Supabase Storage (with quota pre-check; failures persisted).
330
+ 2. Provider session files / session where supported.
331
+ 3. Agent / archive.
332
+ 4. Environment / archive or delete where allowed.
333
+ 5. Skills uploaded for this session and any other ephemeral provider resources created for this run.
334
+ 6. Provider Vault/Credentials created on Claude's side for MCP wiring.
335
+ 7. The antpath per-run Vault entry: `vault.deleteSecret(runs.execution_secret_id)`, then clear the column.
336
+ 8. Local output metadata/storage only when the user deletes a run.
307
337
 
308
338
  Cleanup properties:
309
339
 
@@ -319,10 +349,19 @@ Resource leak recovery:
319
349
  - A reconciliation sweeper reviews unfinished intended rows, expired leases, and provider-listable resources tagged with antpath metadata.
320
350
  - If provider create succeeded but provider id was not persisted before a crash, the sweeper attempts to match by deterministic name/metadata and attach the provider id for cleanup.
321
351
 
322
- Workspace/key deletion:
352
+ Workspace deletion:
323
353
 
324
354
  - Workspace deletion blocks new runs, requests cancellation for active runs, drains cleanup, deletes stored outputs, then purges metadata.
325
- - Provider key revocation blocks new runs immediately and may force existing runs to fail/cancel if cleanup can no longer authenticate.
355
+ - There is no separate "provider key revocation" flow because there is no persistent provider key. Submitting the next run with a different key is the rotation mechanism.
356
+
357
+ ## Audit, rate limits, quotas, and deletion
358
+
359
+ - Abuse-sensitive actions are rate-limited before provider, Vault, Storage, or mutation side effects where practical.
360
+ - Audit logs must never include provider keys, OAuth tokens, MCP credentials, raw prompts, raw outputs, signed URLs, or service-role credentials.
361
+ - Workspace storage quota is enforced before output download/upload when file size is known, with streaming hard caps for unknown-size files.
362
+ - App-user deletion is soft/disable-first; hard deletion must not break historical run, usage, output, or audit references.
363
+ - Workspace deletion is pending-first: block new submissions, mark active runs pending-delete, allow cleanup/storage deletion to drain, then purge only safe metadata.
364
+ - Recovery operations should be driven by explicit cleanup/deletion state and audit logs, not manual DB guessing.
326
365
 
327
366
  ## Run state model
328
367
 
@@ -364,6 +403,8 @@ Error classes:
364
403
  - Database: Supabase Postgres.
365
404
  - Storage: Supabase Storage private bucket.
366
405
  - Secrets: Vercel/Railway environment variables plus Supabase Vault for tenant provider keys.
406
+ - CI: GitHub Actions with pnpm, default lint/test/build/package checks, and a separate local Supabase integration job.
407
+ - Local dev stack: `pnpm dev:stack` starts Supabase without printing local keys, validates local config, checks migrations, builds shared packages, and runs the dashboard and worker together. Missing Auth.js providers do not block local service startup unless `ANTPATH_DEV_STACK_REQUIRE_AUTH_PROVIDER=1` is set.
367
408
 
368
409
  Required runtime config includes:
369
410
 
@@ -374,7 +415,10 @@ Required runtime config includes:
374
415
  - Auth.js secret/providers;
375
416
  - SDK token hashing secret/pepper;
376
417
  - worker identity;
377
- - provider API base/version settings.
418
+ - provider API base/version settings;
419
+ - dashboard BFF rate-limit defaults;
420
+ - worker polling/lease/metrics settings.
421
+ - dev stack ports for dashboard and worker.
378
422
 
379
423
  Environment-configurable defaults include:
380
424
 
@@ -396,26 +440,28 @@ Environment-configurable defaults include:
396
440
  - workspace storage cap;
397
441
  - signed URL TTL;
398
442
  - free user allowance;
399
- - metadata retention toggles.
443
+ - metadata retention toggles;
444
+ - audit/rate-limit windows for sensitive BFF actions.
445
+ - live e2e opt-in flags for provider sessions, public MCP endpoints, Anthropic skill IDs, and timeouts.
400
446
 
401
447
  Missing optional env vars must fall back to conservative low limits. Missing required secret/connectivity env vars must fail service startup.
402
448
 
449
+ Live public MCP/skills e2e tests are intentionally opt-in because they depend on external provider and public MCP availability. Deterministic unit/component tests validate request wiring for MCP server declarations, tool policies, skill references, and secret non-leakage; the live e2e validates the real provider accepts and runs that configuration end to end.
450
+
451
+ No-cost provider resilience and load testing uses sanitized replay snapshots from real Managed Agents sessions. Replay tests inject network disconnects, transient HTTP failures, provider latency, slow SSE chunks, cleanup failures, and concurrent full-session load around those snapshots. Idempotent read/delete-style calls may retry with bounded backoff; provider create/send POST calls are not retried by default because their side effects may already have happened.
452
+
403
453
  ## Open implementation details
404
454
 
405
455
  These are not architecture blockers, but must be pinned during implementation planning:
406
456
 
407
- - Exact plan tier values for duration, concurrency, storage, polling, and free user allowance.
408
- - Exact Auth.js providers.
409
- - Auth.js adapter/session mode and user mirror lifecycle.
410
- - Supabase Vault access method and SQL privilege wrapper shape.
411
- - Signed URL TTL and whether storage paths include random unguessable components in addition to workspace/run ids.
412
- - Deletion audit requirements.
457
+ - Production plan tier values for duration, concurrency, storage, polling, and free user allowance.
413
458
  - Provider metadata naming convention.
414
459
  - Provider list/search capabilities and reconciliation coverage for each resource type.
415
460
  - Exact Claude Managed Agents event pagination/filter semantics and bounded polling fallback.
416
461
 
417
462
  ## Backlog
418
463
 
464
+ - Persistent workspace-level provider connections (BYO Anthropic key custody across runs, with rotation, revocation, and re-use). The MVP submission contract carries the key inline per run instead.
419
465
  - Provider webhooks as wakeup/reconciliation accelerator.
420
466
  - SSE live event stream for richer UI.
421
467
  - Supabase Realtime with explicit Auth.js-to-Supabase authorization design.