{"protocol":"clank-doc/1","frameworkVersion":"0.8.0","slug":"ai-first","title":"AI-first contracts","description":"Clank separates model providers from application contracts. It does not dictate which model or SDK to use. Instead, it makes capabilities discoverable, inputs deterministic, side effects explicit, and the rendered interface machine readable","group":{"id":"start","title":"Start with npm"},"url":"https://docs.clank.run/docs/ai-first","source":"docs/ai-first.md","headings":["Runtime schemas","Actions","Discovery and invocation bridge","Action UI state","Semantic UI","Inspect and operate","Agent described views","Security model"],"tableOfContents":[{"id":"runtime-schemas","title":"Runtime schemas","level":2},{"id":"actions","title":"Actions","level":2},{"id":"discovery-and-invocation-bridge","title":"Discovery and invocation bridge","level":2},{"id":"action-ui-state","title":"Action UI state","level":2},{"id":"semantic-ui","title":"Semantic UI","level":2},{"id":"inspect-and-operate","title":"Inspect and operate","level":2},{"id":"agent-described-views","title":"Agent-described views","level":2},{"id":"security-model","title":"Security model","level":2}],"markdown":"# AI-first contracts\n\nClank separates model providers from application contracts. It does not dictate which model or SDK to use. Instead, it makes capabilities discoverable, inputs deterministic, side effects explicit, and the rendered interface machine-readable.\n\n## Runtime schemas\n\n```ts\nconst CreateTask = s.object({\n  title: s.string({ min: 1, max: 120, description: \"Short task title\" }),\n  priority: s.optional(s.enum([\"low\", \"normal\", \"high\"] as const)),\n  estimate: s.nullable(s.number({ min: 0 })),\n  tags: s.array(s.string(), { max: 10 }),\n});\n\nconst value = CreateTask.parse(modelOutput);\nconst result = CreateTask.safeParse(modelOutput);\nconst jsonSchema = CreateTask.toJSONSchema();\n```\n\nAvailable builders are `string`, `number`, `boolean`, `literal`, `enum`, `unknown`, `array`, `object`, `optional`, `nullable`, and `union`. Objects are strict by default and aggregate nested validation issues with paths. Set `{ strict: false }` to preserve unknown properties.\n\n## Actions\n\n```ts\nconst createTask = defineAction({\n  name: \"tasks.create\",\n  description: \"Create one task in the current workspace.\",\n  input: CreateTask,\n  output: s.object({ id: s.string(), created: s.boolean() }),\n  sideEffects: \"write\",\n  confirmation: \"write\",\n  authorize: (_input, context) => context.user != null,\n  handler: async (input, context) => {\n    return database.create(input, context.user);\n  },\n});\n\nconst output = await createTask(input, { user, signal });\n```\n\nAction names use letters, digits, `.`, `_`, and `-`. Input is always validated before authorization and execution; output is validated when an output schema exists.\n\nSide-effect levels are:\n\n- `none`: pure computation.\n- `read`: external or private data may be read, but not changed.\n- `write`: state may be changed.\n- `destructive`: deletion, irreversible mutation, or similarly sensitive behavior.\n\nConfirmation policy is `never`, `write`, or `always`. The HTTP bridge requires `x-clank-confirmation: confirmed` for applicable write/destructive calls and returns `428 CONFIRMATION_REQUIRED` when it is absent. The host or agent is responsible for obtaining real user confirmation before setting that header; the header is not a substitute for authentication or authorization.\n\n## Discovery and invocation bridge\n\n```ts\nconst bridge = createAgentBridge([createTask, archiveTask]);\n\nbridge.manifest();\nawait bridge.invoke(\"tasks.create\", input, context);\nawait bridge.handle(request, context);\n```\n\nThe manifest protocol is `clank-agent/1`. The Fetch handler supports:\n\n- `GET /.well-known/clank` or `GET /manifest`\n- `POST /actions/:name` with a JSON input body\n\nWrite/destructive example:\n\n```ts\nawait fetch(\"/actions/tasks.create\", {\n  method: \"POST\",\n  headers: {\n    \"content-type\": \"application/json\",\n    \"x-clank-confirmation\": \"confirmed\",\n  },\n  body: JSON.stringify(input),\n});\n```\n\nSuccess is `{ ok: true, output }`. Failures have `{ ok: false, error: { code, message, details? } }` and appropriate 4xx or 500 status codes. Requests require JSON content type, are size-bounded, may be restricted to exact origins, and redact validation input values. Since the bridge consumes and returns Web `Request`/`Response`, it can be mounted in Node, edge, worker, Bun, or compatible server environments.\n\n## Action UI state\n\n```tsx\nconst save = actionRunner(saveDocument);\n\n<button\n  disabled={save.pending.value}\n  onClick={() => save.run({ body: editor.value })}\n>\n  {save.pending.value ? \"Saving…\" : \"Save\"}\n</button>\n```\n\nAn action runner exposes `pending`, `data`, `error`, `canRun`, `run`, and `reset`. Revision tracking prevents an older execution from overwriting newer UI state.\n\n## Semantic UI\n\nOpt-in DOM properties become `data-clank-*` attributes:\n\n```tsx\n<button\n  agentId=\"archive-document\"\n  agentLabel=\"Archive current document\"\n  agentDescription=\"Moves the document out of active results.\"\n  agentAction=\"documents.archive\"\n  intent=\"destructive-control\"\n>\n  Archive\n</button>\n```\n\n`agentHidden: true` removes an area from semantic inspection. It does not visually hide the element and is not an authorization boundary.\n\nOn interactive HTML elements, `agentLabel` is also mirrored to `aria-label`. The same explicit name is therefore available to assistive technology, browser automation, and the Clank semantic surface; non-interactive elements retain only the `data-clank-label` metadata.\n\n## Inspect and operate\n\n```ts\nconst surface = createAgentSurface(document.querySelector(\"#app\")!);\n\nsurface.inspect();\nsurface.input(\"task-title\", \"Ship documentation\");\nsurface.activate(\"add-task\");\n```\n\nThe inspection tree contains interactive or explicitly semantic elements only. It understands explicit agent labels plus native `label`, `aria-labelledby`, roles, IDs, names, required/readonly/invalid/expanded/checked/multiple state, placeholders, form values, link targets, and semantic children. Password and file-input values are never included. This is smaller and more stable than raw HTML, CSS selectors, or screenshots.\n\n`activate` and `input` target explicit `agentId` values or native element IDs. Those methods dispatch ordinary browser events so human and agent interaction use the same application behavior. File inputs are refused; password controls are write-only through this surface.\n\nSchema-aware `createForm()` controllers also expose a `clank-form/1` manifest containing field schemas and suggested controls without live values.\n\n## Agent-described views\n\n`defineView({ name, description, props, render })` attaches `viewManifest` metadata to a component and can validate its props. Use it for reusable surfaces that an external planner or UI generator needs to discover.\n\n## Security model\n\n- Schemas validate shape; authorization still belongs in `authorize` and the handler's data layer.\n- UI semantic IDs expose controls; they do not grant server permission.\n- Never place secrets in labels, descriptions, manifests, validation details, or rendered DOM.\n- Hosts must obtain meaningful user confirmation before sending the bridge confirmation header.\n- Treat all model-generated action input as untrusted even when the model previously inspected the UI.\n"}