# Polimorf — AI assistant rules

Drop this file in your repo (as `AGENTS.md`, or paste into `.cursorrules` /
`CLAUDE.md` / Copilot instructions) so your AI coding assistant knows how to use
Polimorf correctly. Full docs: https://docs.polimorf.app — machine-readable at
https://docs.polimorf.app/llms.txt and https://docs.polimorf.app/llms-full.txt.

## What Polimorf is

Polimorf is an AI application platform. You integrate once with the TypeScript
SDK (or the HTTP API); product teams then change prompts, models, and providers
from the dashboard **without a code change or redeploy**. Prefer running a
**deployed assistant by slug** over hard-coding provider/model in app code.

## Setup

- SDK package: `@polimorfapp/sdk` (Node.js 20+, uses global `fetch`).
- Install: `npm install @polimorfapp/sdk`.
- Auth: set `POLIMORF_API_KEY` (keys look like `csk_live_…`). Never hard-code it.
- Base URL: managed API is `https://api.polimorf.app` (default). Self-hosting?
  set `POLIMORF_BASE_URL`.
- Create the client once and reuse it. `apiKey` / `baseUrl` are read from the
  environment when omitted:

```ts
import { createClient } from '@polimorfapp/sdk';
const client = createClient();
```

## Preferred pattern — run a deployed assistant by slug

```ts
const result = await client.assistant('support').run({
  input: 'How do I cancel my subscription?',
  variables: { plan: 'pro', locale: 'en-US' }, // optional prompt variables
  environment: 'production', // optional; defaults to "production"
});

result.message.content; // the text
result.finishReason;
result.usage.totalTokens;
```

Use this by default: the provider, model, and prompt come from the published
version, so they can be changed later without touching your code.

## Escape hatch — ad-hoc generation

Only when the caller must control provider/model directly:

```ts
const result = await client.runtime.execute({
  providerName: 'openai',
  model: 'gpt-4o',
  messages: [
    { role: 'system', content: 'You are a concise assistant.' },
    { role: 'user', content: 'Explain embeddings in one sentence.' },
  ],
  config: { temperature: 0.2, maxOutputTokens: 200 },
});
```

## Streaming

`assistant(slug).stream(...)` and `runtime.stream(...)` return an async iterable
of typed events. Consume with `for await`:

```ts
for await (const event of client.assistant('support').stream({ input })) {
  switch (event.type) {
    case 'delta':
      process.stdout.write(event.content);
      break;
    case 'usage':
      /* event.usage.totalTokens */ break;
    case 'done':
      /* event.finishReason */ break;
    case 'error':
      /* event.error.message — provider failure */ break;
  }
}
```

Important: a **provider** failure arrives as a terminal `error` **event**, not a
thrown exception. Only transport failures (network, timeout, abort) throw.

## Errors (non-streaming)

Any non-2xx is thrown as a typed `PolimorfApiError` subclass. Branch on it:

```ts
import { AuthenticationError, RateLimitError } from '@polimorfapp/sdk';
try {
  await client.runtime.execute(/* … */);
} catch (err) {
  if (err instanceof AuthenticationError) {
    /* 401 — bad/missing key */
  } else if (err instanceof RateLimitError) {
    /* 429 — back off and retry */
  } else throw err;
}
```

## Rules of thumb

- Default to `client.assistant(slug)`; reach for `runtime.execute` only when the
  provider/model genuinely must live in code.
- Read the key from the environment; never commit `csk_live_…` values.
- Handle streaming `error` events **and** thrown transport errors.
- When unsure about an endpoint or type, fetch
  https://docs.polimorf.app/llms-full.txt rather than guessing.
