Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions .agents/skills/effect-httpapi-workers-patterns/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: effect-httpapi-workers-patterns
description: Build, refactor, or review Effect v4 HttpApi services on Cloudflare Workers. Use when working with effect/unstable/httpapi, HttpApiBuilder, generated Effect clients, typed API contracts, request context, tagged errors, Worker observability, Maple, OpenTelemetry, OTLP, or Cloudflare logs and traces.
description: Build, refactor, or review Effect v4 HttpApi services on Cloudflare Workers. Use when working with effect/http-api, HttpApiBuilder, generated Effect clients, typed API contracts, request context, tagged errors, Worker observability, OpenTelemetry, OTLP, or Cloudflare logs and traces.
---

# Effect HttpApi Workers Patterns
Expand All @@ -12,6 +12,7 @@ Use this skill when editing or reviewing an Effect HttpApi stack, generated Effe
- Keep API contracts separate from server implementation.
- Contract modules define `HttpApi`, `HttpApiGroup`, endpoints, schemas, tagged errors, and security declarations.
- Server route modules only implement `HttpApiBuilder.group(...)` live layers.
- Import `HttpApi`, `HttpApiBuilder`, `HttpApiClient`, and `HttpApiMiddleware` from `effect/http-api`. This repository pins Effect 4.0.3, which does not export `effect/unstable/httpapi` or `effect/httpapi`.
- Prefer named `Schema.Class` response/request types and `Schema.TaggedError` errors with `httpApiStatus`.
- Avoid broad `JsonRecord`, `AnyJson`, and generic `{ error: string }` contracts for real endpoints.
- If a generated client requires `unknown` or extra services, fix the schema contract instead of casting at the call site.
Expand Down Expand Up @@ -44,16 +45,9 @@ Use this skill when editing or reviewing an Effect HttpApi stack, generated Effe
- Production should use metadata/configurable rates.
- Keep invocation logs enabled until application logs are proven complete.
- Use Cloudflare Observability destinations for platform OTEL export when possible.
- If also exporting Effect app spans/logs via `effect/unstable/observability/Otlp`, pass a base OTLP URL. Do not pass a URL that already ends in `/v1/traces`, `/v1/logs`, or `/v1/metrics` because Effect appends those paths.
- Keep OTLP headers and tokens in secrets/env vars; never in source or non-secret metadata.

## Maple With Effect

- Use `@maple-dev/effect-sdk/cloudflare` for Worker-local Effect logs and traces.
- Provide the Maple telemetry layer to the same runtime that runs route handlers; do not run route telemetry in a separate runtime.
- Flush Maple telemetry at the Worker request boundary with `ctx.waitUntil(telemetry.flush(env))` after the response path has scheduled its Effect logs/spans.
- In local dev, use `OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4318` and run `maple start` before the app dev command.
- Avoid duplicate exports: route code should use Effect logs/spans, the Worker should provide one Maple layer, and Cloudflare Worker Observability should remain the platform collection path for deployed stages.
- If a Worker also exports Effect app spans or logs, import `Otlp` from `effect/observability/Otlp`. Pass a base OTLP URL. Do not pass a URL that already ends in `/v1/traces`, `/v1/logs`, or `/v1/metrics`, because Effect appends those paths.
- Keep OTLP headers and tokens in secrets or env vars. Never put them in source or non-secret metadata.
- This repository does not install Maple (`@maple-dev/effect-sdk`). Deployed logs and traces go through Cloudflare Worker Observability. Do not add a Maple layer or a local `maple start` collector here.

## Cloudflare Workers General

Expand Down
55 changes: 29 additions & 26 deletions apps/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,36 +123,39 @@ nub run typecheck # Type check

## Programmatic Client

For headless or script usage, the `BackgroundSessionsClient` is available:
Scripts call the typed `S0Api` client, `makeS0ApiClient` from `@solzero/api`. Pass a user API key
as `bearerToken`. User API keys start with `oiak_`, and the client sends `Authorization: Bearer`.

```ts
import { BackgroundSessionsClient } from "./src/session-client"

const apiKey = process.env.S0_API_KEY
if (!apiKey) {
throw new Error("S0_API_KEY is required")
import { Effect } from "effect"
import { makeS0ApiClient } from "@solzero/api"

function createSession(apiKey: string) {
return Effect.gen(function* () {
const client = yield* makeS0ApiClient({
baseUrl: "http://localhost:3000/api",
bearerToken: apiKey,
})

return yield* client.sessions.create({
payload: {
sessionKind: "isolate",
repoOwner: "cloudflare",
repoName: "sandbox-sdk",
title: "Investigate flaky e2e test",
},
})
})
}

const client = new BackgroundSessionsClient({
auth: { kind: "api-key", apiKey },
baseUrl: "http://localhost:3000/api",
})

const session = await client.createSession({
repoOwner: "cloudflare",
repoName: "sandbox-sdk",
title: "Investigate flaky e2e test",
})
```

Browser code should instead use `auth: { kind: "browser-session" }` with the same-origin
`/api` BFF. Do not embed a user API key in a browser bundle.
Browser code uses `runApi` from `src/lib/api-client.ts`. That client calls the same-origin `/api`
prefix and sends the session cookie. Do not embed a user API key in a browser bundle.

## API Routes
## API

- `/api/auth/api-keys` - create/list user API keys
- `/api/auth/[...all]` - Better Auth proxy routes
- `/api/sessions` - Session CRUD (proxies to control plane)
- `/api/sessions/[id]/*` - Session archive, prompt, ws-token (proxies)
- `/api/repos` - Repository list (proxies)
- `/api/secrets` - Global secrets (proxies)
`S0Api` in `@solzero/api` is the public HTTP contract. Callers use the `/api` prefix on the web
origin. A deployed Cloudflare stage sends `/api` to the API Worker on a path route. Local
development and `workers.dev` ingress use the TanStack `/api/$` route, which forwards the request
through the `S0_API` service binding. The binding uses the same Worker entrypoint and the same
endpoint authentication.
1 change: 1 addition & 0 deletions packages/api/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export function controlPlaneAuthClient(options: { bearerToken?: string | Redacte
)
}

/** Builds the typed `S0Api` client. `bearerToken` is sent as `Authorization: Bearer`. */
export function makeS0ApiClient(options: {
baseUrl: string | URL
bearerToken?: string | Redacted.Redacted
Expand Down
Loading