> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nika.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Compile a workflow

> Ask the engine for a candidate workflow from a skeleton, an intent or an edit, review it, write it, then run it.

`compile()` asks the engine for a candidate `.nika` workflow and runs nothing.
Every outcome is data: `ready`, `incomplete` and `refused` all resolve, with
the candidate, the engine's questions and its diagnostics. It works on both
doors; this page uses the local engine.

## Without a model

Offline, the deterministic compiler resolves the exact skeleton names
`nika compile --list` shows, settles bounded intents (such as filtering rows
of a file), and edits the constants of an existing workflow. No provider is
called, whatever keys your environment holds.

```ts compile.mts theme={"system"}
import { Nika } from '@supernovae-st/nika'

const nika = new Nika({ cwd: process.cwd() })

// 1 · Create from a skeleton, answering its one question.
const created = await nika.compile({
  intent: 'classify-and-route',
  answers: { 'const.request': 'An outage affects support customers.' },
}, { timeoutMs: 15_000 })
console.log(created.status, created.diagnostics[0]?.message)
// ready Filled the existing semantic hole from an explicit answer.

// 2 · Edit the candidate: a conservative constant change, nothing else moves.
if (created.ready && created.candidate !== null) {
  const edited = await nika.compile({
    workflow: created.candidate,
    change: { set_constant: { name: 'request', value: 'One customer is affected.' } },
  })
  console.log(edited.status) // ready
}

// 3 · Free text: the compiler asks only for what is missing.
const open = await nika.compile({ intent: 'Every morning at 9, summarize ./inbox/*.md into ./digest.md' })
console.log(open.status, open.questions.map((q) => q.key))
// incomplete [ 'model', 'trigger.timezone', 'trigger.missed', 'trigger.overlap', 'trigger.ceiling' ]
```

The third request's summary needs a model every time it runs, so the
compiler asks which one (`model`). Its "every morning at 9" is a trigger, not
a task: the engine records it as `requested_trigger` (`cron: 0 9 * * *`) for a
schedule to bind later, and the candidate's bytes carry no cadence. The
`trigger.*` questions are not mandatory: they belong to that later schedule.

## Answer the questions

`nextCompileRequest(request, outcome, answers)` builds the next round from an
incomplete outcome, keyed by each question's `key`. The deterministic
compiler settles this request's structure; only the run-time model is open:

```ts answer.mts theme={"system"}
import { Nika, isNikaCompileHeld, nextCompileRequest, type NikaCompileRequest } from '@supernovae-st/nika'

const nika = new Nika({ cwd: process.cwd() })

let request: NikaCompileRequest = {
  intent: 'Summarize ./notes/launch.md in three bullet points and write them to ./out/summary.md.',
}
let outcome = await nika.compile(request)
console.log(outcome.status, outcome.questions.map((q) => q.key)) // incomplete [ 'model' ]

if (outcome.status === 'incomplete' && !isNikaCompileHeld(outcome)) {
  request = nextCompileRequest(request, outcome, { model: 'deepseek/deepseek-flash' })
  outcome = await nika.compile(request)
}
console.log(outcome.status, outcome.compile_version) // ready 1
```

`compile_version: 1` means no provider was called. The answer only names the
model the workflow will use when it runs; nothing is spent until you run it,
with that provider's key.

<video controls muted playsInline preload="none" poster="/images/posters/sdk-compile.png" aria-label="node author.mts with the intent Every morning, summarize ./inbox/*.md into ./digest.md prints round 1 incomplete with one required question, model, and four optional ones, and every morning as a trigger, not a task; answering model = mock/echo gives round 2 ready with no provider calls and a daily schedule that requires binding; the candidate checks clean as digest.nika, its permits read ./inbox/**, write ./digest.md and call nika:assert, nika:glob, nika:jq, nika:read and nika:write, and nika check plans it in nine waves" alt="node author.mts with the intent Every morning, summarize ./inbox/*.md into ./digest.md prints round 1 incomplete with one required question, model, and four optional ones, and every morning as a trigger, not a task; answering model = mock/echo gives round 2 ready with no provider calls and a daily schedule that requires binding; the candidate checks clean as digest.nika, its permits read ./inbox/**, write ./digest.md and call nika:assert, nika:glob, nika:jq, nika:read and nika:write, and nika check plans it in nine waves">
  <source src="https://mintcdn.com/supernovae-acdf3706/BQMaEHUNhf9JYml1/videos/sdk-compile.mp4?fit=max&auto=format&n=BQMaEHUNhf9JYml1&q=85&s=8042d9177f4c0cc36d089c7009301c69" type="video/mp4" />
</video>

**One sentence, two rounds, no model call.** `author.mts` sends `Every morning, summarize ./inbox/*.md into ./digest.md`. Round 1 is `incomplete`: one required question, the run-time `model`, four optional ones, and `every morning` recorded as a trigger, not a task. Answering `model = mock/echo` through `nextCompileRequest()` makes round 2 `ready`, with no provider call; the daily schedule still `requires_binding`. The candidate checks clean and is kept as `digest.nika`: it asks to read `./inbox/**`, write `./digest.md` and call `nika:assert`, `nika:glob`, `nika:jq`, `nika:read` and `nika:write`, and `nika check` plans it in nine waves. Nothing runs. *Recorded in a terminal from `@supernovae-st/nika` 0.123.0 and its bundled engine `nika 0.123.0 (0bfde3765)`.*

> **Transcript: every screen of the recording, in order**
>
> ```text theme={"system"}
> $ node author.mts "Every morning, summarize ./inbox/*.md into ./digest.md"
> round 1  incomplete · 1 required, 4 optional question(s)
>          ? model
>            Which explicit runtime provider/model should run the language steps (extract, classify, draft)?
>          · `every morning` is a trigger, not a task
> answer   model = mock/echo (the offline rehearsal model)
> round 2  ready · provider calls: none
>          trigger schedule · daily · requires_binding
> check    clean · exit 0 · kept as digest.nika
> $
>
> ── screen cleared ──
>
> $ sed -n '/^permits:/,/^tasks:/p' digest.nika      # the boundary the candidate asks for
> permits:
>   fs:
>     read:
>     - ./inbox/**
>     write:
>     - ./digest.md
>   tools:
>   - nika:assert
>   - nika:glob
>   - nika:jq
>   - nika:read
>   - nika:write
> tasks:
> $
>
> ── screen cleared ──
>
> $ ./node_modules/.bin/nika check digest.nika | head -n 11
> nika check · digest.nika
>  ✔ PLAN     9 waves · 9 tasks · max parallelism 1
>       wave 1 glob_source (invoke · nika:glob)
>       wave 2 glob_found (invoke · nika:jq)
>       wave 3 glob_admit (invoke · nika:assert)
>       wave 4 read_source (invoke · nika:read)
>       wave 5 documents (invoke · nika:jq)
>       wave 6 draft (infer · mock/echo)
>       wave 7 draft_anchors (invoke · nika:jq)
>       wave 8 draft_admit (invoke · nika:assert)
>       wave 9 write_output (invoke · nika:write)
> $
> ```

## The candidate is source, not a run

A ready `candidate` is ordinary `.nika` text. Review it, write it to a file,
then `run()` it: the normal admission applies again.

```ts theme={"system"}
import { writeFile } from 'node:fs/promises'

const hello = await nika.compile('hello')
if (hello.ready && hello.candidate !== null) {
  await writeFile('hello.nika', hello.candidate)
  const run = await nika.run('hello.nika')
  console.log((await run.result()).status) // succeeded
}
```

Locally, a request can name `output` to have the engine write a ready
candidate for you; the outcome then reports `written`:

```ts theme={"system"}
const outcome = await nika.compile({ intent: 'hello', output: 'compiled-hello.nika' })
console.log(outcome.status, outcome.written) // ready compiled-hello.nika
```

* `requested_boundary` and the source-only `check_preview` grant no
  authority: `nika check` and `nika run` judge the real environment.
* Compile creates no run, job, approval or proof. A local engine keeps its
  plan records under `.nika/compile/`; an HTTP compile writes nothing.
* `signal` and `timeoutMs` stop only the compile request.
* An outcome has `compile_version` 1 when no provider was called, 2 when one
  was, with its receipt in `provenance.authoring`.

## With a model

A provider reads free intent only when the request opts in, and you choose
the model:

| Door | How to opt in |
| - | - |
| Local engine | `authoringModel: 'provider/name'` in the request; the engine reads that provider's key from its own environment |
| `nika serve` | `cognition: 'explicitProvider'`, on a server started with `nika serve --authoring-model <provider/name>` (its `/health` then lists `compileNativeV2`) |

```ts theme={"system"}
// Needs DEEPSEEK_API_KEY in the engine's environment; the provider meters the call.
const outcome = await nika.compile({
  intent: 'Read ./data/orders.csv and write the paid total per customer to ./out/totals.csv.',
  authoringModel: 'deepseek/deepseek-flash',
})
console.log(outcome.compile_version, outcome.status) // 2 when a provider was called
```

Answer its questions with `nextCompileRequest()` as above. When the verifier
does not accept a candidate, the outcome stays `incomplete` and
`isNikaCompileHeld(outcome)` is true: that candidate is a preview, never a
workflow to run. Over HTTP, `cognition: 'explicitProvider'` on a default
server, one started without `--authoring-model`, throws
`NikaCompatibilityError` (`capability: 'compileNativeV2'`) instead of falling
back.

> **Which version is this?**
>
> > [!NOTE]
> >
> > These pages describe the published `@supernovae-st/nika@0.123.0`
> > [SDK release](https://github.com/supernovae-st/nika-client/tree/v0.123.0) and its matching
> > [bundled engine](https://github.com/supernovae-st/nika/releases/tag/v0.123.0). A newer engine
> > release or SDK main commit does not change an installed package. Use Node.js
> > 22 or newer, on macOS (arm64, x64) or Linux with glibc (x64, arm64); no
> > global Nika installation is required for local mode.

> **[Bounded authoring](https://docs.nika.sh/examples/bounded-authoring.md)**
>
> The skeletons and bounded intents the deterministic compiler settles.

> **[The terminal Session](https://docs.nika.sh/getting-started/session.md)**
>
> The same compiler, in a conversation.

> **[Method index](https://docs.nika.sh/sdk/reference/methods.md)**
>
> Every method and the door it needs.

> **[Types](https://docs.nika.sh/sdk/reference/types.md)**
>
> `NikaCompileOutcome` and its fields.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.