# Forge AI v1

Forge AI is Forge-owned, profile-scoped model routing for deployed applications.
Applications declare what an AI use case may do; Forge supplies the private
runtime capability, provider credential, budget enforcement, and metadata-only
usage receipt.

Forge AI is not a raw OpenAI proxy. Application code cannot choose provider
URLs, credentials, arbitrary model IDs, gateway headers, or another tenant's
billing identity.

Forge AI is one service in the [Forge Sites application-services
stack](forge-sites-services.md). Site owners can discover it from the
repository's **Sites** page, then configure it beside Build and Identity in
`forgeBuild.ts`. The future Forge Agent is a separate roadmap capability; Forge
AI does not grant an end user agent or repository authority.

## What is available now

| Capability | Current Alpha contract |
| --- | --- |
| Logical model route | `forge/text-fast@1` |
| Funding | Host funded only |
| Runtime | Server-side, non-streaming text messages |
| Native Forge JS | `env.forge.ai.responses.create(...)` |
| Connected Cloudflare Worker | `env.FORGE_AI.responsesCreate(...)` |
| Structured output | JSON mode and JSON parsing; the application must validate the object |
| Privacy | Prompt and completion content is not retained in Forge logs or usage rows |
| Production provider for connected Workers | Workers AI, selected by Forge |

BYOK, browser-direct invocation, streaming, tools, images, audio, embeddings,
arbitrary models, caller-defined provider headers, and public HTTPS inference
are not available. Forge has internal OpenAI-compatible adapters for OpenAI,
Gemini, and Featherless, but applications cannot select them and connected
Workers currently always use Workers AI.

## 1. Declare a named profile

Add the policy to `app.ai.profiles` in the project's `forgeBuild.ts`. Profile
names are the application-facing API; provider and model details stay behind
the logical Forge route.

```ts
import { defineForge, importWrangler } from '@smolai/forge/config';

export default defineForge({
  version: 1,
  name: 'overgrid',
  build: { command: 'pnpm run build', workingDirectory: '.' },
  app: {
    entrypoint: 'dist/server/index.js',
    assets: { directory: 'dist/client' },
    ai: {
      profiles: {
        gameCommentary: {
          model: 'forge/text-fast@1',
          audience: 'server',
          funding: ['host'],
          maxInputTokens: 2_500,
          maxOutputTokens: 180,
          response: {
            type: 'json_schema',
            name: 'game_commentary',
            schema: {
              type: 'object',
              additionalProperties: false,
              required: ['line', 'factIds'],
              properties: {
                line: { type: 'string', maxLength: 320 },
                factIds: {
                  type: 'array',
                  minItems: 1,
                  maxItems: 12,
                  items: { type: 'string' },
                },
              },
            },
          },
          limits: {
            requestsPerMinute: 10,
            hostCostMicrosPerDay: 1_000_000,
            previewHostCostMicrosPerDay: 50_000,
          },
          privacy: { contentLogging: 'off' },
        },
      },
    },
  },
  routes: [
    { pattern: '/assets/*', to: 'app.assets' },
    { pattern: '/*', to: 'app.http' },
  ],
  provider: {
    cloudflare: {
      wrangler: importWrangler('wrangler.toml', {
        shareProductionResources: true,
      }),
    },
  },
});
```

Use `audience: 'server'` for connected Workers. Although the manifest parser
reserves `signed_in` and `anonymous`, connected RPC does not carry visitor
identity and there is no browser-direct capability in this release.

Changing or adding a profile changes the immutable deployment policy. Commit
the `forgeBuild.ts` update and publish a new Forge release before calling the
new profile.

## 2. Call Forge AI from server code

### Connected Cloudflare applications

Forge publishes one private, Forge-managed service binding into the connected
Worker Version:

```ts
interface ForgeAiBinding {
  responsesCreate(input: {
    profile: string;
    input: Array<{
      role: 'system' | 'user' | 'assistant';
      content: string;
    }>;
    billing?: { source: 'host' };
    idempotencyKey: string;
  }): Promise<ForgeAiResult>;
}

interface Env {
  FORGE_AI?: ForgeAiBinding;
}

const result = await env.FORGE_AI.responsesCreate({
  profile: 'gameCommentary',
  input: [
    {
      role: 'system',
      content: 'Use only the supplied approved public facts.',
    },
    {
      role: 'user',
      content: JSON.stringify({ facts: approvedPublicFacts }),
    },
  ],
  billing: { source: 'host' },
  idempotencyKey: `commentary:${eventDigest}`,
});
```

Do not declare `FORGE_AI` in `wrangler.toml`. Forge owns that binding and
rejects repository-authored or inherited conflicts. The binding targets the
private `ConnectedForgeAi` RPC entrypoint; there is no public inference URL.

Forge seals the capability to one account, project, release, provider-target
generation, manifest digest, and profiles digest. Those values are revalidated
against mutable account, entitlement, project, target, and production-pointer
state on each call. Application code never supplies them.

### Native Forge JS applications

Native Forge JS receives the same normalized service through the trusted
runtime facade:

```ts
const result = await env.forge.ai.responses.create({
  profile: 'gameCommentary',
  input: [{ role: 'user', content: JSON.stringify(approvedPublicFacts) }],
  billing: { source: 'host' },
  idempotencyKey: `commentary:${eventDigest}`,
});
```

## 3. Validate the result and keep a fallback

```ts
interface ForgeAiResult {
  id: string;
  outputText: string;
  outputJson?: unknown;
  route: 'forge/text-fast@1';
  usage: {
    inputTokens: number;
    outputTokens: number;
    estimatedCostMicros: number;
    billedTo: 'host';
    cached: false;
  };
  receipt: {
    policyDigest: string;
    pricingVersion: string;
    limitResetAt?: string;
  };
}
```

`response.type: 'json_schema'` requests JSON output and causes Forge to parse it
into `outputJson`. In this Alpha, Forge does not validate the generated object
against the supplied JSON Schema. Validate the shape, length, allowed fact IDs,
and product-specific invariants in application code before displaying it.

Use an application timeout and retain deterministic behavior:

```ts
const fallback = deterministicCommentary(approvedPublicFacts);

if (!env.FORGE_AI) return fallback;

try {
  const result = await withTimeout(
    env.FORGE_AI.responsesCreate(request),
    5_000,
  );
  return validateCommentary(result.outputJson) ?? fallback;
} catch {
  return fallback;
}
```

Forge AI should normally explain or transform an authoritative application
result. It should not become the source of truth for game moves, permissions,
prices, scoring, or other deterministic product behavior.

## Request bounds

- A deployment may declare 1–32 profiles.
- Profile names match `[A-Za-z][A-Za-z0-9_-]{0,63}`.
- A request contains 1–64 messages.
- Roles are `system`, `user`, or `assistant` and each message is bounded to
  100,000 characters.
- `idempotencyKey` is required and contains 8–128 characters.
- `maxInputTokens` defaults to 2,500 and may be 1–32,000.
- `maxOutputTokens` defaults to 256 and may be 1–4,096.
- Input admission currently estimates tokens from message characters.
- Effective request and cost ceilings are the lowest of the profile, account
  entitlement, and Forge platform ceilings. App-authored limits can lower a
  ceiling but cannot raise one.
- Preview and production cost allowances are enforced separately.

`estimatedCostMicros` is the current maximum-output reservation estimate, not a
reconciled provider invoice. `cached` is currently always `false`.

## Idempotency and retries

Derive the key from the immutable logical event and normalized input. Reusing a
key with different input returns `idempotency_conflict` and never starts a
second provider call. Reusing the same key and input prevents a second charge,
but this Alpha does not replay the previous output; callers currently receive a
provider-unavailable failure instead. Keep a successful result in application
state when needed and use the deterministic fallback after an ambiguous retry.

## Stable failures

Forge normalizes failures to these codes:

- `ai_disabled`
- `profile_not_found`
- `identity_required`
- `byok_consent_required`
- `budget_exhausted`
- `rate_limited`
- `input_too_large`
- `output_invalid`
- `provider_unavailable`
- `idempotency_conflict`

Native runtime failures expose the code on the returned error. Connected RPC
callers should catch defensively; the capability may be absent in local
development, disabled by entitlement or project state, rate limited, or unable
to complete provider work.

## Browser and abuse boundary

Browser code must call a narrow application-owned server route. The application
route remains responsible for request-body validation, authentication or
Turnstile, per-user abuse controls, response validation, and a bounded timeout.
`audience: 'server'` protects the Forge capability; it does not automatically
protect a public application endpoint from abuse.

`/.forge/ai` is reserved but provides no browser or cross-account capability in
this release. A future signed HTTPS transport is needed only for Workers in a
different Cloudflare account or applications outside Cloudflare.

## OverGrid production pattern

OverGrid currently has two deployed profiles:

- `gameCommentary`: short structured `{ line, factIds }` output with 2,500
  input tokens and 180 output tokens.
- `trainingCoach`: structured `{ answer, factIds }` output with 4,000 input
  tokens and 300 output tokens.

Both are host-funded, server-only, limited to 10 requests per minute, use a
five-second application timeout, validate model output against approved fact
IDs, and fall back to deterministic explanations. Commentary receives only the
public tactical receipt after the authoritative game engine commits a move.
The training coach also uses an application session, Turnstile, and rate
limiters.

For local tests, mock `FORGE_AI.responsesCreate`. Forge injects the real binding
only into published connected Worker Versions.

## Coding-agent checklist

Tell an application coding agent to:

1. Call Forge AI only from Worker/server code.
2. Reuse an already-declared profile, or update `forgeBuild.ts` and publish a
   new Forge release before using a new profile.
3. Send bounded, approved evidence rather than raw hidden or privileged state.
4. Use a stable 8–128-character content-derived idempotency key.
5. Apply an application timeout and locally validate `outputJson`.
6. Preserve a deterministic fallback for missing binding, timeout, invalid
   output, rate limits, and provider failure.
7. Never add provider API keys, a Wrangler `FORGE_AI` binding, raw model IDs,
   browser-direct calls, streaming, tools, or arbitrary upstream URLs.
