This page is the authoritative inventory of Jaiph configuration keys: every key, its value type, default, environment-variable equivalent, and precedence. For environment-variable details (defaults, scopes) see Environment variables. For the CLI flags that front-end the same knobs see CLI.
Configuration sources, in priority order:
config { … } — applies for the duration of that def.config { … } — applies to all defs in that file unless overridden.| Aspect | Rule |
|---|---|
| Module-level | At most one config { … } block per .jh file. May appear anywhere among top-level constructs. |
| Def-level | At most one nested config { … } per def body. Must be the first non-comment construct in the body. |
| Allowed module-level keys | agent.*, run.*, and module.*. |
| Allowed def-level keys | agent.* and run.*. A module.* key in a def-level block is E_PARSE (module.* keys are not allowed in def-level config (only agent.* and run.* keys)). Any other unrecognized key is the unknown-key error below — including the removed trusted_envs. |
| Duplicate block | E_PARSE duplicate config block (only one allowed per file) / E_PARSE duplicate config block inside def (only one allowed per def). |
| Unknown key | E_PARSE unknown config key: <key>. Allowed: … (lists every allowed key). |
| Wrong value type | E_PARSE. |
Global rule: Every Jaiph string position accepts three equivalent forms:
| Form | Example | Stored as |
|---|---|---|
| Bare identifier | model |
${model} |
| Double-quoted string | "${model}" or "prefix-${model}" |
string content as-is |
| Bare interpolation ref | ${model} or ${model.field} |
${model} / ${model.field} |
All three resolve identically at runtime. The only interpolation Jaiph understands is ${name} / ${name.field} — shell expansion forms (${var:-default}, ${var//…}, ${#var}) are not. Written as a bare config value they fail with E_PARSE (config value must be a quoted string, bare identifier, or true/false); written inside a double-quoted config value they are stored as literal text and never expanded.
| Type | Format | Example |
|---|---|---|
| String | Double-quoted; supports \\, \n, \t, \", and ${name} interpolation |
"gpt-4", "${model}" |
| String (bare identifier sugar) | Bare identifier — stored as ${name} and resolved at runtime |
model |
| String (bare ref sugar) | Bare ${name} or ${name.field} — stored as-is and resolved at runtime |
${model}, ${model.field} |
| Boolean | Bare true / false |
true |
| Integer | Unsigned decimal digits | 300 |
String config values support the same ${identifier} interpolation as orchestration strings (log, prompt, return, etc.). A bare identifier or bare ${name} on the RHS is sugar for a single ${identifier} reference (for example agent.model = model, agent.model = ${model}, and agent.model = "${model}" are all equivalent).
Interpolation scope:
| Config level | Available identifiers |
|---|---|
| Module-level | Module const values and environment variables |
| Def-level | Module const values, environment variables, and that def’s parameters |
Interpolation runs when the config scope is applied (def entry for def-level keys; CLI startup for module-level keys). Environment variables still win over in-file config when locked.
| Key | Type | Default | Env equivalent | Notes |
|---|---|---|---|---|
agent.model |
string | — | JAIPH_AGENT_MODEL (env only) |
Model for prompt steps in this scope. Resolved at each prompt invocation and passed as a per-call --model flag — it does not set JAIPH_AGENT_MODEL in the run environment, so scripts and other steps do not see it. Set JAIPH_AGENT_MODEL in the shell to override all prompts in a run. |
agent.command |
string | cursor-agent |
JAIPH_AGENT_COMMAND |
Cursor backend command. Basename other than cursor-agent enables custom-command mode (stdin → command → stdout). Entry module only — imported modules cannot set this key by default (see Import trust boundary). |
agent.backend |
string (cursor | claude | codex) |
cursor |
JAIPH_AGENT_BACKEND |
Backend selector. Entry module only — imported modules cannot set this key by default (see Import trust boundary). |
agent.trusted_workspace |
string (path) | workspace root | JAIPH_AGENT_TRUSTED_WORKSPACE |
Directory passed to Cursor as --trust. When unset, defaults to JAIPH_WORKSPACE. A relative path in the entry module’s module-level config is resolved against the workspace root to an absolute path at CLI startup. Values applied at runtime — a def-level block or an imported module — are assigned to the env var exactly as authored (not normalized). |
agent.cursor_flags |
string | — | JAIPH_AGENT_CURSOR_FLAGS |
Extra flags appended to Cursor invocations (whitespace-split). |
agent.claude_flags |
string | — | JAIPH_AGENT_CLAUDE_FLAGS |
Extra flags appended to Claude invocations (whitespace-split). |
| Key | Type | Default | Env equivalent | Notes |
|---|---|---|---|---|
run.logs_dir |
string (path) | .jaiph/runs |
JAIPH_RUNS_DIR |
Step log directory. Relative paths join the workspace root; absolute paths are used as-is. |
run.debug |
boolean | false |
JAIPH_DEBUG |
Enable debug tracing. |
run.recover_limit |
integer | 10 |
— (no env override) | Maximum attempts for run … recover loops before the step fails. Resolves via def > module > default. |
Informational metadata only; does not affect execution. Allowed in module-level config only — any module.* key inside a def-level config is E_PARSE.
| Key | Type | Default |
|---|---|---|
module.name |
string | — |
module.version |
string | — |
module.description |
string | — |
use + --env)The trusted_envs config key is removed (E_PARSE unknown config key). Scripts are sterile by default: a script subprocess receives only process mechanics, the JAIPH_* script contract keys, and the host keys its own declaration requests with a use clause — and a use key crosses only when the operator granted it with --env KEY[=VALUE]:
script release use GITHUB_TOKEN NPM_TOKEN = 'gh release create …'
jaiph run --env GITHUB_TOKEN --env NPM_TOKEN publish.jh
The use request lives on a script declaration (named script, export script, or import script … as alias use KEY) or on a named prompt definition (prompt analyze(log) use GITHUB_TOKEN = …) — never on defs, call or prompt sites, or anonymous prompt steps. Anonymous prompt subprocesses keep the fail-closed scrubPromptEnv allowlist and never receive --env secrets in their child env; a named prompt’s granted use keys are injected into its agent subprocess on top of that scrub. That is spawn-env, not a sandbox — see Why Jaiph. Recipe: Pass a host key to a script. Also Language — Subprocess environment and Environment variables.
| Layer | Effect |
|---|---|
Environment (JAIPH_AGENT_*, JAIPH_RUNS_DIR, JAIPH_DEBUG) |
Locked when present in the parent env; cannot be overridden by module- or def-level config. |
Def-level config |
Applies for the def body; restored on exit. |
Module-level config |
Applies to defs without their own block. |
| Built-in defaults | Lowest priority. |
| Call type | Scope behaviour |
|---|---|
Root entry (jaiph run file.jh) |
Full module + def metadata applied with normal precedence. |
| Same-module call | Callee’s def-level config is layered on top of the caller’s effective env. Module-level config is not re-applied. |
Cross-module call (e.g. alias.main()) |
Callee’s module-level config is layered, then def-level on top — same as root-entry precedence, respecting ${NAME}_LOCKED. The entry-module-only keys (agent.command, agent.backend, agent.trusted_workspace, agent.cursor_flags, agent.claude_flags, run.logs_dir) are not applied from imported modules (see Import trust boundary). |
After any nested call returns, the caller’s scope is restored exactly as before.
When the host CLI builds the runner environment, any of these variables already present in process.env gets a matching ${NAME}_LOCKED=1 flag set on the child env. The runtime refuses to overwrite a locked value from later metadata merges.
Locked names: JAIPH_AGENT_BACKEND, JAIPH_AGENT_MODEL, JAIPH_AGENT_COMMAND, JAIPH_AGENT_TRUSTED_WORKSPACE, JAIPH_AGENT_CURSOR_FLAGS, JAIPH_AGENT_CLAUDE_FLAGS, JAIPH_RUNS_DIR, JAIPH_DEBUG.
agent.command and agent.backend are execution-binary keys — they determine which process runs prompt steps. To prevent a third-party .jh library from silently redirecting execution to a different binary, these two keys may only be set from the entry module’s config {} block (module-level or def-level). Imported modules that declare agent.command or agent.backend in their config {} are silently ignored for these keys.
agent.trusted_workspace, agent.cursor_flags, agent.claude_flags, and run.logs_dir carry the same entry-module-only restriction. They shape the agent trust path (passed to Cursor as --trust), the agent argv (cursor_flags / claude_flags are appended to the invocation), and the run directory — so an imported module setting them could redirect trust, alter the agent command line, or move run logs for its own prompt steps. Imported modules that declare these keys in their config {} are silently ignored for them.
Host secrets carry a related boundary: a script — imported or local — receives a host key only through its own use clause and an explicit operator --env grant, so a library cannot pull host secrets into its steps without the operator naming each key on the command line (see Script env keys).
The remaining config keys (agent.model, run.debug) are not restricted and follow the normal scoping rules for cross-module calls.
Advanced unlock (use with caution): to allow an imported module to override an entry-module-only key, set the matching environment variable before the run:
| Variable | Effect |
|---|---|
JAIPH_AGENT_COMMAND_IMPORT_UNLOCK=1 |
Allow any imported module to set agent.command. |
JAIPH_AGENT_BACKEND_IMPORT_UNLOCK=1 |
Allow any imported module to set agent.backend. |
JAIPH_AGENT_TRUSTED_WORKSPACE_IMPORT_UNLOCK=1 |
Allow any imported module to set agent.trusted_workspace. |
JAIPH_AGENT_CURSOR_FLAGS_IMPORT_UNLOCK=1 |
Allow any imported module to set agent.cursor_flags. |
JAIPH_AGENT_CLAUDE_FLAGS_IMPORT_UNLOCK=1 |
Allow any imported module to set agent.claude_flags. |
JAIPH_RUNS_DIR_IMPORT_UNLOCK=1 |
Allow any imported module to set run.logs_dir. |
The matching ${NAME}_LOCKED=1 flag still applies on top — a locked key cannot be changed regardless of the source.
| In-file key | Environment variable |
|---|---|
agent.model |
(prompt-scoped only — does not set JAIPH_AGENT_MODEL) |
agent.command |
JAIPH_AGENT_COMMAND |
agent.backend |
JAIPH_AGENT_BACKEND |
agent.trusted_workspace |
JAIPH_AGENT_TRUSTED_WORKSPACE |
agent.cursor_flags |
JAIPH_AGENT_CURSOR_FLAGS |
agent.claude_flags |
JAIPH_AGENT_CLAUDE_FLAGS |
run.logs_dir |
JAIPH_RUNS_DIR |
run.debug |
JAIPH_DEBUG |
run.recover_limit |
(no env override) |
module.name |
(no env override) |
module.version |
(no env override) |
module.description |
(no env override) |
Variables with no in-file equivalent: JAIPH_CODEX_API_URL (codex endpoint), JAIPH_PROMPT_RETRY / JAIPH_PROMPT_RETRY_DELAYS (prompt-retry schedule), OPENAI_API_KEY (codex credential). Full inventory in Environment variables.
| Backend | Required credential | Endpoint / CLI |
|---|---|---|
cursor (default) |
CURSOR_API_KEY (or stored cursor-agent login on host runs) |
Runs agent.command (default cursor-agent) with stream-json framing. |
claude |
ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN (or stored Claude CLI login on host runs) |
Runs claude on PATH. |
codex |
OPENAI_API_KEY |
Calls the OpenAI Chat Completions endpoint directly (JAIPH_CODEX_API_URL overrides the URL). No CLI-login fallback. |
Backend-specific flags come from agent.cursor_flags / agent.claude_flags (or the matching env vars). There is no per-prompt backend override.
Before jaiph run spawns the runner, the host CLI runs a credential pre-flight (src/cli/run/preflight-credentials.ts). It collects the distinct backend(s) declared in the entry file’s module-level config block and each def-level block, plus the effective default (JAIPH_AGENT_BACKEND env, or cursor when unset). Deeper per-import overrides resolved at runtime are not followed.
| Backend | Required credential | Host behaviour |
|---|---|---|
codex |
OPENAI_API_KEY |
hard error (E_AGENT_CREDENTIALS) |
claude |
ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN |
not checked (CLI login is the host path) |
cursor |
CURSOR_API_KEY |
warn (CLI login may still work) |
Hard errors exit non-zero with no runner launched. Warnings go to stderr and the run proceeds. Skip cases: entry file declares no explicit backend and uses no prompt step → no pre-flight; jaiph run --raw → no pre-flight.
Every error and warning names the backend, the model when agent.model is set, the entry .jh file, the config scope, and the concrete remedy. The config scope is one of module config, def <name>, JAIPH_AGENT_BACKEND env, or default.
Resolution order for a prompt step:
| Step | Source | Notes |
|---|---|---|
| 1 | User env — JAIPH_AGENT_MODEL non-empty. |
model_reason: explicit. Applies to every prompt in the run. |
| 2 | In-file config — agent.model from def-level then module-level metadata (interpolated at prompt time). |
model_reason: explicit. Applies to that prompt invocation only; passed as --model to the backend CLI without writing JAIPH_AGENT_MODEL. |
| 3 | Flags model — --model <name> inside agent.cursor_flags / agent.claude_flags. |
model_reason: flags. Codex has no flag channel; this step does not apply. |
| 4 | Backend default — Cursor/Claude binaries pick their own. Codex defaults to gpt-4o in code. |
model_reason: backend-default. |
For the Claude backend, when agent.model is set and agent.claude_flags does not already contain --model, Jaiph passes --model <value> to the Claude CLI automatically. When agent.claude_flags already contains its own --model, Jaiph does not add a second one, so the model in agent.claude_flags is the one the Claude CLI receives. When both are set this way, the PROMPT_START / PROMPT_END records still carry model_reason: explicit with the agent.model value, even though the Claude CLI ran with the model from agent.claude_flags.
PROMPT_START / PROMPT_END records in run_summary.jsonl carry model (resolved string, or null when the backend auto-selects) and model_reason (explicit, flags, backend-default, or none for a custom agent command, which has no model concept).
NodeWorkflowRuntime retries transport-failed prompt invocations on an escalating backoff schedule before propagating the failure to any enclosing recover / catch.
| Attempt | Delay before this attempt |
|---|---|
| 1 | (none — initial call) |
| 2 | 15s |
| 3 | 1m |
| 4 | 10m |
| 5 | 30m |
| 6 | 2h |
Total worst-case wall-clock: ~2h41m.
Only transport failures are retried (non-zero exit from cursor/claude, codex HTTP error, spawn failure). Deterministic post-processing failures — invalid JSON, schema validation — fail on the first attempt and return { ok: false }.
Each attempt emits its own PROMPT_START / PROMPT_END and STEP_START / STEP_END. Each failure logs a LOGERR line; the final termination logs another. The captured value reflects the successful attempt.
| Variable | Effect |
|---|---|
JAIPH_PROMPT_RETRY=0 |
Disable retry entirely (one attempt, fail on transport failure). |
JAIPH_PROMPT_RETRY_DELAYS |
Comma-separated list of non-negative integer milliseconds. Invalid entries abort the prompt. |
jaiph test defaults JAIPH_PROMPT_RETRY=0. Backoff sleep is interruptible: run abort, SIGINT, or SIGTERM cancels the pending wait without further backend calls.
The retry backoff above handles a backend that fails. A separate set of watchdogs handles a backend that hangs — it never exits, so without them the runtime would block on the subprocess indefinitely (no commit, no queue progress, no retry). Each prompt invocation installs three independent layers over the spawned backend process:
| Layer | Variable | Default | Trigger | Outcome |
|---|---|---|---|---|
| Completion grace | JAIPH_PROMPT_COMPLETION_GRACE_SECONDS |
30 |
The backend emitted its terminal result event (work is done) but the process has not exited within the grace window. |
Terminate the process, return success with the captured answer. |
| Idle timeout | JAIPH_PROMPT_IDLE_TIMEOUT_SECONDS |
900 (15m) |
No stdout/stderr for the whole window — the backend is stuck mid-work. | Terminate the process, return failure → feeds the retry backoff. |
| Absolute cap | JAIPH_PROMPT_MAX_SECONDS |
7200 (2h) |
Total wall-clock for the single invocation exceeds the cap, regardless of activity. | Terminate the process, return failure → feeds the retry backoff. |
Set any variable to 0 to disable that layer. The idle timer resets on every chunk of backend output, so a slow-but-active run is bounded only by the absolute cap.
The completion-grace layer specifically addresses the known claude -p failure mode where the CLI streams its final answer (and the terminal result event) but the process never exits — often because a descendant it spawned is still holding the output pipe open. When a watchdog fires it terminates the backend’s whole process tree (via killProcessTree; see Architecture) with SIGTERM, escalating to SIGKILL after 5s, and tears down the runtime’s handles on the child’s stdio so a lingering descendant cannot keep the run alive. On Windows the tree is force-killed with taskkill /T on the first signal, so the SIGKILL escalation is a no-op.
The prompt watchdogs above bound a single backend call. Jaiph also has two controls that bound the whole run, and both are off by default, so existing runs behave as before.
JAIPH_RUN_TIMEOUT sets a parent-enforced wall-clock cap, in seconds, for a run. Without this cap, the only automatic stop is a manual Ctrl-C, because the host spawn installs only SIGINT and SIGTERM handlers and the prompt watchdogs cover a single backend call. When the cap is reached, the parent terminates the run child’s whole process group with SIGTERM and escalates to SIGKILL after a short grace period (via killProcessTree; see Architecture), so the run stops without a manual Ctrl-C, and the failure footer shows E_RUN_TIMEOUT. Set it to 0, leave it empty, or give it an invalid value to disable it, which restores the earlier behaviour where only a manual SIGINT or SIGTERM stops a run.
JAIPH_MAX_STEPS sets an optional max-step circuit breaker in the runtime. When you set it to a positive integer, the runtime counts every executed step across the whole run, and it counts loop iterations and nested or recursive calls but skips trivia. Once the count goes past the cap, the runtime logs E_MAX_STEPS, aborts the run, and returns a failure, so a runaway program stops on its own without a manual signal. Set it to 0, leave it empty, or give it an invalid value to disable the breaker.
The prompt watchdogs bound a single backend call, and the run timeout and step cap bound the whole run. A separate watchdog bounds one leaf step by how long it stays silent. A leaf step is a script step or a prompt step, and the watchdog watches its stdout and stderr. It catches a script step that the prompt watchdogs never see, such as an overnight npm run test:ci that stops producing output but never exits. The runtime checks for silence on a poll interval set by JAIPH_STEP_IDLE_WARN_CHECK_MS (default 5000 milliseconds, floor 250).
JAIPH_STEP_IDLE_WARN_SEC (default 180 seconds; 0 disables) sets how long a leaf step can go without any stdout or stderr before the runtime emits a LOGWARN. The warning repeats on the same cadence while the step stays silent, so at 180 seconds, then 360, then 540, and so on. Any new output resets the clock. The warning does not fail the step, and it only surfaces a stalled backend or a long-running command.
JAIPH_STEP_IDLE_KILL_SEC (default 3600 seconds, one hour; 0 disables) sets a hard limit for a script step. When a script step goes that long without any stdout or stderr, the runtime emits a LOGERR naming the step and how long it was silent, terminates the step’s subprocess with SIGTERM and then SIGKILL (via killProcessTreeEscalating; see Architecture), and fails the step. Any new output resets this clock too. The warn cadence and the kill limit run off the same idle clock but fire independently. The kill applies to script steps only, because a prompt step’s backend is already bounded by the prompt watchdogs above, so a prompt step still gets the idle warnings but is never killed by this limit.
agent.command is consumed by the cursor backend only. For claude and codex, Jaiph always invokes the Claude CLI or the codex HTTP path, regardless of agent.command.
When agent.backend = "cursor" and agent.command’s basename is anything other than cursor-agent, Jaiph treats it as a custom agent command:
| Channel | Behaviour |
|---|---|
| stdin | Prompt text piped to the command. |
| stdout | Captured as the prompt response (raw text, no JSON framing). |
| stderr | Passes through to the terminal. |
Cursor-specific flags (--output-format, --stream-partial-output, --workspace, …) |
Not appended. |
| Step display name | Command basename instead of cursor. |
Custom commands still participate in PROMPT_START / PROMPT_END, write artifacts, and apply returns schema validation when configured.
jaiph test does not call resolveRuntimeEnv. The test runner spreads process.env, then sets JAIPH_TEST_MODE, JAIPH_WORKSPACE, JAIPH_RUNS_DIR (ephemeral), JAIPH_SCRIPTS, and mock fields (JAIPH_MOCK_RESPONSES_JSON, JAIPH_MOCK_PROMPT_ARMS_JSON). JAIPH_*_LOCKED flags are not set unless inherited from the parent env. NodeWorkflowRuntime still applies in-file config via applyMetadataScope with the same lock rules.
Agent and run settings are visible inside defs and scripts as JAIPH_* environment variables. In orchestration strings, ${IDENT} resolves against def bindings first, then against the process environment.
jaiph initjaiph init creates .jaiph/bootstrap.jh, .jaiph/SKILL.md, and .jaiph/.gitignore. There is no separate config file — config { … } blocks live in program source. See CLI — jaiph init.
config block syntax in the formal grammar.