--- title: Agent Skill permalink: /jaiph-skill redirect_from: - /jaiph-skill.md --- # Jaiph Bootstrap Skill (for Agents) **Why this page exists.** Agentic work needs the same things human teams need: a clear sequence of steps, explicit checks, and a record of what ran. Jaiph is a small workflow language for that: **workflows** sequence orchestration, **rules** express checks, **`script`** holds real shell, and the runtime logs steps and writes run artifacts. The payoff is behavior that is easier to repeat, verify, and debug than ad-hoc shell snippets alone. ## Overview This page is an **agent skill**: it tells an AI assistant how to **author** Jaiph workflows (`.jh` files) and what a sensible `.jaiph/` layout looks like. It is not a full language specification — use [Getting started](getting-started.md) as the documentation map, [Grammar](grammar.md) for syntax and validation details, [Configuration](configuration.md) for `config` keys, [Inbox & Dispatch](inbox.md) for channels, and [Sandboxing](sandboxing.md) for rule design vs optional Docker isolation. **Jaiph** is a small language for agentic workflows: **orchestration** (rules, prompts, managed calls) and **shell in `script` definitions**. The **Node workflow runtime** (`NodeWorkflowRuntime`) interprets the parsed AST in process — there is no separate transpiled workflow shell on the execution path ([Architecture](architecture.md)). Before `jaiph run` or `jaiph test`, **`buildScripts()`** takes a single **entry** `.jh` path (the workflow file, or the `*.test.jh` file for tests), runs **compile-time validation** (`validateReferences` inside **`emitScriptsForModule`**), and writes extracted **`script`** files under `scripts/` for that module and every file reachable from it via transitive **`import`** — not the whole workspace unless those files are imported. **`jaiph compile`** runs the same **`validateReferences`** checks by parsing each module in the computed closure **without** **`buildScripts`**, script emission, or the runner ([Architecture](architecture.md)). The runner’s **`buildRuntimeGraph()`** then loads the graph with **parse-only** imports (it does not re-run `validateReferences`). **Contracts (CLI vs runtime):** **Live:** `__JAIPH_EVENT__` JSON lines on **stderr only** (CLI progress and **hooks** — hooks are **CLI-only**, driven by that stream). **Durable:** `.jaiph/runs/...` and **`run_summary.jsonl`**. Channels are enforced at compile time and executed in the runtime (in-memory queue + inbox files under the run dir); they are not hooks. The **JS kernel** (`src/runtime/kernel/`) handles **prompt** execution, **managed script subprocesses**, **inbox** queues and dispatch, and **event/summary emission**. **Rule** bodies run in-process; user **`script`** bodies run as separate OS processes (bash by default, polyglot via fence lang tags like `` ```node ``, `` ```python3 `` or a leading `#!` shebang in the body). **Test lane:** `jaiph test` runs **`*.test.jh`** in-process (`node-test-runner.ts`): for each file it calls **`buildScripts(testFile, …)`** (same helper as `jaiph run`, with the **test file as the entry** so its import closure is validated and scripts are emitted), then **`buildRuntimeGraph(testFile)` once per file**, mocks, and assertions — same `NodeWorkflowRuntime` as `jaiph run`. The runtime enables **`suppressLiveEvents`** for those workflow runs so **`__JAIPH_EVENT__`** lines are not written to **stderr** (keeping `node --test` output readable); **`run_summary.jsonl`** under the run directory is still updated where the emitter records workflow traffic ([Architecture](architecture.md)). **After `jaiph init`**, a repository gets `.jaiph/bootstrap.jh` (a triple-quoted prompt that tells the agent to read `.jaiph/SKILL.md`) and a copy of this file. The bootstrap prompt asks the agent to scaffold workflows under `.jaiph/` and to end with a clear `WHAT CHANGED` + `WHY` summary. The expected outcome is a **minimal workflow set** for safe feature work: preflight checks, an implementation workflow, verification, and a `workflow default` entrypoint that wires them together (with an optional human-or-agent “review” step when you use a task queue). Docker-backed runs use the official `ghcr.io/jaiphlang/jaiph-runtime` image by default; see [Sandboxing](sandboxing.md) to override with `runtime.docker_image` or `JAIPH_DOCKER_IMAGE`. **Concepts:** - **Rules** — Structured checks: `ensure` (other **rules** only), `run` (**scripts** only — not workflows), `const`, `match`, `if`, `for … in …` (line iteration over a string binding), `fail`, `log`/`logerr`, `return "…"` / `return run script()` / `return ensure rule()`, `ensure … catch`, `run … catch`, `run … recover`. No raw shell lines, `prompt`, inbox send/route, or `run async`. Under `jaiph run`, rule bodies are executed **in-process** by the Node runtime; when a rule runs a **script**, that script is a normal managed subprocess (same as scripts from workflows) — see [Sandboxing](sandboxing.md). - **Workflows** — Named sequences of **managed** Jaiph steps (`ensure`, `run`, `prompt`, `const`, `fail`, `return`, `log`/`logerr`, inbox **send**, `match`, `if`, `for … in …`, `run async`, `ensure`/`run` with `catch` or `recover`, …) plus optional **inline shell** lines: a line that does not parse as a managed step is treated as bash stored in a `shell` AST node (validated like other shell text). Prefer top-level **`script`** definitions and `run` for multi-line or reusable shell. Route declarations (`->`) belong on top-level `channel` lines, never inside a workflow body (a `->` in a body is `E_PARSE`). - **Scripts** — Top-level **`script`** definitions are **bash (or shebang interpreter) source**, not Jaiph orchestration. Defined with `` script name = `body` `` (single-line backtick) or `` script name = ```[lang] ... ``` `` (fenced block). Double-quoted string bodies (`script name = "body"`) and bare identifier bodies (`script name = varName`) are **removed** — both produce parse errors with guidance to use backtick delimiters. The compiler treats all script bodies as **opaque text**: it does not parse lines as Jaiph steps, reject keywords, strip quotes, or validate cross-script calls. This means embedded `node -e` heredocs, inline Python, `const` assignments in JS, and any other valid shell construct compile without interference. Jaiph interpolation (`${...}`) is **forbidden** in **single-line backtick** script bodies — use `$1`, `$2` positional arguments to pass data from orchestration to scripts. In **fenced** (triple-backtick) blocks, `${...}` is passed through to the shell as standard parameter expansion (`${VAR}`, `${VAR:-default}`, etc.). A single-backtick body containing a newline is a hard parse error — use a fenced block for multi-line scripts. Use `return N` / `return $?` for exit status and **stdout** (`echo` / `printf`) for string data to callers. From a **workflow** or **rule**, call with **`run fn()`**. Can be exported (`export script name = ...`) for use by importing modules. Cannot be used with `ensure`, are not valid inbox route targets, and must not be invoked through `$(...)` or as a bare shell step. **Polyglot scripts:** use a fence lang tag (`` ``` ``) to select an interpreter — the tag maps directly to `#!/usr/bin/env `. Any tag is valid (no hardcoded allowlist). For example: `` ```node ``, `` ```python3 ``, `` ```ruby ``, `` ```lua ``. Alternatively, if no fence tag is present, the first non-empty body line may start with `#!` (e.g. `#!/usr/bin/env lua`), which becomes the script's shebang and the body is emitted verbatim (you cannot combine a fence tag with a manual shebang — that is an error). Without either, `#!/usr/bin/env bash` is used and the emitter applies only lightweight bash-specific transforms (`return` normalization, `local`/`export`/`readonly` spacing, import alias resolution). Scripts are extracted to a `scripts/` directory under the run output tree (`jaiph run --target ` sets that tree; without `--target` the CLI uses a temporary directory) and executed via **`JAIPH_SCRIPTS`**. **Inline scripts:** For trivial one-off commands, use `` run `body`(args) `` or `` run ```lang...body...```(args) `` directly in a workflow or rule step instead of declaring a named `script` definition. The body (single backtick for one-liners or triple backtick for multi-line) comes before the parentheses; optional comma-separated arguments go inside the parentheses: `` run `echo $1`("hello") ``. Fenced blocks support lang tags for polyglot inline scripts: `` run ```python3 ... ```() ``. Capture forms: `` const x = run `echo val`() `` and `` const x = run ```...```() ``. The old `run script() "body"` form is **removed** — use the backtick forms instead. Inline scripts use deterministic hash-based artifact names (`__inline_`) and run with the same isolation as named scripts. `run async` with inline scripts is not supported. - **Channels** — Top-level `channel [-> workflow, ...]` declarations with optional inline routing; **send** uses `channel_ref <- …`. Routes are declared on the channel declaration, not inside workflow bodies (see [Inbox & Dispatch](inbox.md)). Channel names share the per-module namespace with rules, workflows, scripts, and module-scoped `local` / `const` variables. Step semantics (`ensure`, `run`, `prompt`, `catch`, `recover`, `match`, `if`, `for`, `log`, `fail`, `return`, `send`, `run async`) are detailed in the **Steps** section below. **Audience:** Agents that produce or edit `.jh` files. --- ## Safe delivery loop (any repository) Use this loop whenever you add or change Jaiph workflows so failures surface before work is handed back. When the repo defines a **`workflow default` entrypoint** (often `.jaiph/main.jh`) that wires preflight → implementation → verification, use **`jaiph run`** on that file for end-to-end delivery after the narrower checks below pass. 1. **Preflight** — Run the project’s readiness checks if they exist (often `jaiph run .jaiph/readiness.jh` or a named preflight workflow). When the repo ships native tests (`*.test.jh`), run `jaiph test` before large edits when practical. 2. **Implement** — Edit `.jh` modules using only constructs described in [Grammar](grammar.md); keep managed-call rules (`ensure` for rules, `run` for workflows and scripts); put multi-line or reusable bash in **`script`** definitions (rules **never** allow raw shell lines — use `run` to a script; workflows may use optional inline shell where the grammar allows, but prefer `script` + `run` for anything non-trivial — see [Grammar — Language concepts](grammar.md#language-concepts)). 3. **Format** — Run `jaiph format ` on all authored or modified `.jh` files before committing. This normalizes whitespace, indentation, and top-level ordering (imports, config, and channels hoisted to the top; everything else kept in source order). Use `jaiph format --check ` to verify formatting without writing (non-zero exit on drift — useful in CI). 4. **Compile check** — Run `jaiph compile ` on the paths you touched (or `jaiph compile --json …` in automation). Same `validateReferences` checks as before a run, without executing workflows or writing `scripts/` ([Architecture](architecture.md)). With a **directory** argument, only non-test `*.jh` files are used as entrypoints (`*.test.jh` is skipped); pass a test file path explicitly to validate it. 5. **Verify** — Run `jaiph test` (whole workspace or a focused path) and any verification workflow the repo defines (commonly `jaiph run .jaiph/verification.jh`). Fix failures you introduce. 6. **Inspect (optional)** — Browse `.jaiph/runs` directly when you need raw step logs or `run_summary.jsonl` instead of only the terminal tree. **CLI commands:** | Command | Purpose | |---|---| | `jaiph run [--target ] [--raw] [--] [args...]` | Execute `workflow default` in the given file (`--raw`: no banner/tree/hooks; used for embedding and Docker inner runs) | | `jaiph test [path]` | Run `*.test.jh` test files (workspace, directory, or single file) | | `jaiph format [--check] [--indent ] ` | Reformat `.jh` files (or verify formatting without writing) | | `jaiph compile [--json] [--workspace ] <.jh files or dirs…>` | Parse and `validateReferences` only (no script emission, no run) | | `jaiph init [workspace]` | Scaffold `.jaiph/` with bootstrap workflow and skill file | | `jaiph install [--force] [ …]` | Clone libraries into `.jaiph/libs/` or restore from `.jaiph/libs.lock` | | `jaiph use ` | Reinstall Jaiph at a specific version or nightly | **File shorthand:** `jaiph ./file.jh` auto-routes — `*.test.jh` files run as tests, other `*.jh` files run as workflows. Full flags and environment variables: [CLI Reference](cli.md). --- ## When to Use This Guide Use this guide when generating or updating `.jaiph/*.jh` workflows for a repository after `jaiph init`. ## Source of Truth When this skill conflicts with the compiler or runtime, follow the implementation. For language rules and validation codes, [Grammar](grammar.md) is the detailed reference. Published docs: [jaiph.org](https://jaiph.org). `jaiph init` writes this skill to `.jaiph/SKILL.md` when the installer resolves a skill file: if **`JAIPH_SKILL_PATH`** is set, it is used **only when that path exists on disk**; otherwise the CLI searches install-relative locations and `docs/jaiph-skill.md` from the current working directory ([CLI Reference](cli.md)). If no file is found, init skips `SKILL.md` — set **`JAIPH_SKILL_PATH`** to an existing markdown file (for example `docs/jaiph-skill.md` in a checkout) and run `jaiph init` again. Ignore any outdated Markdown that contradicts the above. ## What to Produce A **minimal workflow set** under `.jaiph/` that matches the delivery loop above: 1. **Sandbox baseline (optional)** — If the repo uses Docker sandboxing, confirm `runtime.docker_image` / `JAIPH_DOCKER_IMAGE` match the tooling the team needs; the default is `ghcr.io/jaiphlang/jaiph-runtime` (see [Sandboxing](sandboxing.md)). 2. **Preflight** — Rules and `ensure` for repo state and required tools (e.g. clean git, required binaries). Expose a small workflow (e.g. `workflow default` in `readiness.jh`) that runs these checks. 3. **Review (optional)** — A workflow that reviews queued tasks before development starts (any filename, e.g. `ba_review.jh`). An agent prompt evaluates the next task for clarity, consistency, conflicts, and feasibility, then either marks it as ready or exits with questions. The implementation workflow gates on this marker so unreviewed tasks cannot proceed. This repository’s `.jaiph/architect_review.jh` is one concrete example; it uses `QUEUE.md` as the task queue. 4. **Implementation** — A workflow that drives coding changes (typically via `prompt`), e.g. `workflow implement` in `main.jh`. When using a task queue, the implementation workflow should check that the first task is marked as ready (e.g. via a `` marker) before proceeding. 5. **Verification** — Rules and a `workflow default` for lint/test/build (e.g. `verification.jh`). Complement this with repo-native `*.test.jh` suites run by `jaiph test` where appropriate. 6. **Entrypoint** — A single `workflow default` (e.g. in `.jaiph/main.jh`) that runs: preflight → (optional) review → implementation → verification. This is what `jaiph run .jaiph/main.jh "..."` executes. Prefer composable modules over one large file. ## Language Rules You Must Respect - **Imports:** `import "path.jh" as alias`. Path must be double-quoted. Path is relative to the importing file first; if no file is found and the path contains `/`, the resolver falls back to project-scoped libraries under `/.jaiph/libs/` (e.g. `import "queue-lib/queue" as queue` resolves to `.jaiph/libs/queue-lib/queue.jh`). If the path has no extension, the compiler appends `.jh`. Install libraries with `jaiph install `. **Script imports:** `import script "./helper.py" as helper` imports an external script file and binds it as a local script symbol — callable with `run helper(args)` exactly like an inline `script` definition. The path resolves relative to the importing file. Shebangs in the imported file are preserved. Missing targets fail with `E_IMPORT_NOT_FOUND`. - **Definitions:** `channel name` (inbox endpoint); `rule name() { ... }` or `rule name(params) { ... }`, `workflow name() { ... }` or `workflow name(params) { ... }`, `` script name = `body` `` or `` script name = ```[lang] ... ``` ``. **Parentheses are required on all rule and workflow definitions** — even when parameterless (e.g. `workflow default() { ... }`, `rule check() { ... }`). Omitting `()` before `{` is a parse error with a fix hint. Named parameters go inside the parentheses — e.g. `workflow implement(task, role) { ... }`, `rule gate(path) { ... }`. At runtime, named params are the only way to access arguments. The compiler validates call-site arity when the callee declares params. Named scripts require a name at the definition site; for anonymous one-off commands use inline scripts: `` run `echo ok`() `` or `` run ```...```(args) ``. Optional `export` before `rule`, `workflow`, or `script` marks it as public (see [Grammar](grammar.md)). Optional `config { ... }` at the top of a file sets agent, run, and runtime options. An optional `config { ... }` block can also appear inside a `workflow { ... }` body (before any steps) to override module-level settings for that workflow only — only `agent.*` and `run.*` keys are allowed; `runtime.*` and `module.*` yield `E_PARSE` (see [Configuration](configuration.md#workflow-level-config)). Config values can be quoted strings, booleans (`true`/`false`), bare integers, or bracket-delimited arrays of strings (see [Grammar](grammar.md) and [Configuration](configuration.md)). - **Module-scoped variables:** `local name = value` or `const name = value` (same value forms). Prefer **`const`** for new files. Values can be single-line `"..."` strings, triple-quoted `"""..."""` multiline strings, or bare tokens. A double-quoted string that spans multiple lines is rejected — use `"""..."""` instead. Accessible as `${name}` inside orchestration strings in the same module. Names share the unified namespace with channels, rules, workflows, and scripts — duplicates are `E_PARSE`. Not exportable; module-scoped only. - **Steps:** - **ensure** — `ensure ref()` or `ensure ref(args…)` runs a rule (local or `alias.rule_name`). **Parentheses are required on every call site**, including zero-argument calls (`ensure check()`, not bare `ensure check`). Arguments are comma-separated inside `()`. **Bare identifier arguments** are supported and preferred (when valid): `ensure check(status)` is equivalent to `ensure check("${status}")` — the identifier must reference a known variable (`const`, capture, or named parameter); unknown names fail with `E_VALIDATE`. **Standalone `"${identifier}"` in call arguments is rejected** — use the bare form instead. Quoted strings with extra text (e.g. `"prefix_${name}"`) stay valid. Jaiph keywords cannot be used as bare identifiers. Optionally `ensure ref(…) catch () `: the recovery body runs **once** on failure (no built-in retry on `ensure` — use `run … recover` for loops). The binding receives merged stdout+stderr from the failed rule. Full output also lives in **`.out` / `.err`** artifacts. Works in workflows and rules. - **run** — `run ref()` or `run ref(args…)` runs a workflow or script (local or `alias.name`). Same **required `()` on every call site** as `ensure`, including zero args (`run setup()`). In a **workflow**, the target may be another workflow or a script; in a **rule**, the target must be a **script** only (`E_VALIDATE` if you name a workflow). **`run` does not forward CLI positional args implicitly** — the entry workflow binds them into named params and must pass values explicitly into callees. **Bare identifier arguments** follow the same rules as `ensure` when applicable. **Nested managed calls inside argument lists must use keywords:** `run foo(run bar())`, `run foo(ensure check())`; bare `run foo(bar())`/`run foo(\`...\`())` forms are rejected. Optionally `catch ()` (runs once on failure, mutually exclusive with `recover`) or `recover ()` (repair-and-retry loop; attempt cap is **`run.recover_limit`** from the **file’s top-level** `config { … }`, default **10** — the runtime does not apply this setting from a workflow’s inner `config` block). **`catch` / `recover` on `run`** are allowed in workflows and rules (rules: callee must remain a script). Also **inline scripts**: `` run `body`(args) `` or `` run ```lang...body...```(args) `` — see Scripts above. - **log** — `log "message"` writes the expanded message to **stdout** and emits a **`LOG`** event; the CLI shows it in the progress tree at the current depth. Double-quoted string; `${identifier}` interpolation works at runtime. For multiline messages, use triple quotes: `log """..."""`. **Bare identifier form:** `log foo` (no quotes) expands to `log "${foo}"` — the variable's value is logged. Works with `const`, capture, and named parameters. **Inline capture interpolation** is also supported: `${run ref([args])}` and `${ensure ref([args])}` execute a managed call and inline the result (e.g. `log "Got: ${run greet()}"`). Nested inline captures are rejected. **`LOG`** events and `run_summary.jsonl` store the **same** message string (JSON-escaped for the payload). No spinner, no timing — a static annotation. See [CLI Reference](cli.md) for tree formatting. Useful for marking workflow phases (e.g. `log "Starting analysis phase"`). - **logerr** — `logerr "message"` is identical to `log` except the message goes to **stderr** and the event type is **`LOGERR`**. In the progress tree, `logerr` lines use a red `!` instead of the dim `ℹ` used by `log`. Same quoting, interpolation, bare identifier, and triple-quote rules as `log` (e.g. `logerr err_msg`, `logerr """..."""`). - **Send** — After `<-`, use a **double-quoted literal**, **triple-quoted block** (`channel <- """..."""`), **`${var}`**, or **`run ref([args])`**. An explicit RHS is always required — bare `channel <-` (without a value) is invalid. Raw shell on the RHS is rejected — use `const x = run helper()` then `channel <- "${x}"`, or `channel <- run fmt_fn()`. Combining capture and send (`name = channel <- …`) is `E_PARSE`. See [Inbox & Dispatch](inbox.md). - **Route** — Routes are declared **at the top level** on channel declarations: `channel name -> workflow_ref` or `channel name -> wf1, wf2`. A `->` inside a workflow body is a **parse error** with guidance to move it to the channel declaration. When a message arrives on the channel, the runtime calls each listed **workflow** (local or `alias.workflow`), binding the dispatch values (message, channel, sender) to the target's 3 declared parameters. Route targets must declare exactly 3 parameters. Scripts and rules are not valid route targets. The dispatch queue drains after the orchestrator completes. **`NodeWorkflowRuntime` does not cap dispatch iterations** — avoid circular sends that grow the queue without bound. See [Inbox & Dispatch](inbox.md). - **Bindings and capture** — `const name = …` (the `const` keyword is required for all captures). All bindings are **immutable**: a name bound by a parameter, `const`, capture, or `script` cannot be rebound in the same scope — the compiler rejects it with `E_VALIDATE: cannot rebind immutable name "…"`. For **`ensure`** / **`run` to a workflow or rule**, capture is the callee’s explicit **`return "…"`**. For **`run` to a script**, capture follows **stdout** from the script body. **`prompt`** capture is the agent answer. **`const`** RHS cannot use `$(...)` or disallowed `${...}` forms — use a **`script`** and `const x = run helper(…)`. **`const`** must not use a **bare** `ref(args…)` call shape: use **`const x = run ref(args…)`** (or **`ensure`** for rules), not **`const x = ref(args…)`** — the compiler fails with **`E_PARSE`** and suggests the **`run`** form. Do not put Jaiph symbols inside `$(...)` — use `ensure` / `run`. See [Grammar](grammar.md#immutable-bindings) and [Grammar](grammar.md#step-output-contract). - **return** — `return "value"` / `return "${var}"` / `return """..."""` sets the managed return value. Also supports **direct managed calls**: `return run ref()` or `return run ref(args)` and `return ensure ref()` or `return ensure ref(args)` — these execute the target and use its result as the return value, equivalent to `const x = run ref(args)` then `return "${x}"`. Parentheses are required on all call sites. - **fail** — `fail "reason"` or `fail """..."""` aborts with stderr message and non-zero exit (workflows; fails the rule when used inside a rule). - **run async** — `run async ref([args...])` starts a workflow or script concurrently and returns a **`Handle`**. Capture is supported: `const h = run async ref()`. The handle resolves on first non-passthrough read (string interpolation, passing as arg to `run`, comparison, conditional, match subject). Passthrough (initial capture, re-assignment) does not force resolution. Unresolved handles are implicitly joined at workflow exit. `recover` (retry loop) and `catch` (single-shot) composition work with `run async`: `run async foo() recover(err) { … }`. Workflows only — rejected in rules. - **match** — `match var { "literal" => …, /regex/ => …, _ => … }` pattern-matches on a string value. The subject is always a bare identifier (no `$` or `${}`). Arms are tested top-to-bottom; the first match wins. Patterns: double-quoted string literal (exact match), `/regex/` (regex match), or `_` (wildcard — exactly one required). Usable as a statement, as an expression (`const x = match var { … }`), or with `return` (`return match var { … }`). Using `$var` or `${var}` as the match subject is a parse error. Allowed in both workflows and rules. See [Grammar](grammar.md#match). - **if** — `if var == "value" { … }` or `if var =~ /pattern/ { … }`. Subject is a bare identifier. Operators: `==` (exact string equality), `!=` (inequality), `=~` (regex match), `!~` (regex non-match). Operand is a `"string"` for `==`/`!=` or `/regex/` for `=~`/`!~`. Body is a brace block of valid workflow/rule steps. No `else` branch — use `match` for exhaustive value branching. `if` is a statement (no value production; cannot use with `const` or `return`). Allowed in both workflows and rules. - **for** — `for iterVar in sourceVar { … }` runs the body once per **line** of the string bound to `sourceVar` (newline-separated text, e.g. from `const`/`prompt`/`run` capture). Each iteration binds `iterVar` to one line (trimming rules match the runtime’s line split — a trailing empty line after a final newline is not an extra iteration). Allowed in workflows and rules. See [Grammar](grammar.md) for the formal production. - **Prompts:** Three body forms: (1) **single-line string** `prompt "..."` — double-quoted, single line only; (2) **identifier** `prompt myVar` — uses the value of an existing binding; (3) **triple-quoted block** `prompt """ ... """` — for multiline text, opening `"""` on the same line as `prompt`. Triple backticks (`` ``` ``) in prompt context are rejected with guidance — they are reserved for scripts. Multiline double-quoted strings are rejected — use a triple-quoted block instead. All forms support `${identifier}` interpolation (`${varName}`, `${paramName}`). **Inline capture interpolation** is also supported: `${run ref([args])}` and `${ensure ref([args])}` inside the prompt string or triple-quoted body (e.g. `prompt "Fix: ${ensure get_diagnostics()}"`). Nested inline captures are rejected. Bare `$varName` is not valid in orchestration strings. `$(...)` and `${var:-fallback}` are rejected. Capture: `const name = prompt "..."`, `const x = prompt myVar`, `const y = prompt """ ... """`. Optional **typed prompt:** `const name = prompt "..." returns "{ field: type, ... }"` or `const name = prompt myVar returns "..."` (flat schema; types `string`, `number`, `boolean`) validates the agent's JSON and sets `${name}` plus per-field variables accessible via **dot notation** — `${name.field}`. Dot notation is validated at compile time: the variable must be a typed prompt capture and the field must exist in the schema. **Orchestration bindings are strings:** typed fields are coerced with `String()` after JSON validation, so e.g. a numeric field is still the text `"42"` in scope. See [Grammar](grammar.md). **Quick reference examples:** ```jaiph # catch — one-shot failure handling ensure ci_passes() catch (failure) { prompt "CI failed — fix the code." run deploy(env) } # recover — repair-and-retry loop (retries until success or limit) run deploy(env) recover(err) { log "Deploy failed: ${err}" run auto_repair(env) } # match — value branching (statement and expression forms) const label = match status { "ok" => "success" /err/ => "something went wrong" _ => "unknown" } # if — conditional guard (no else; use match for exhaustive branching) if env == "" { fail "env was not provided" } if mode =~ /^debug/ { log "Debug mode enabled" } # for — iterate over lines of a string variable const paths = """ docs/a.md docs/b.md """ for path in paths { log "${path}" } # typed prompt — structured JSON with dot-notation field access const result = prompt "Analyze this code" returns "{ type: string, risk: string }" log "Type: ${result.type}, Risk: ${result.risk}" # const capture — from run, ensure, prompt const tag = run get_version() const ok = ensure validate(tag) const answer = prompt "Summarize the changes" # inline scripts — one-off commands without a named script definition run `echo $1`("hello") const ts = run `date +%s`() ``` Conventions: - `jaiph run ` executes `workflow default` in that file. The file must define a `workflow default` (the runtime checks for it and exits with an error if missing). - Inside a workflow, reach other workflows/scripts with **`run ref()`**. Free-form bash can appear as **inline shell** lines when the grammar allows; prefer **`script`** + **`run`** for anything non-trivial. Never use `fn args` or `$(fn …)` as a substitute for **`run`**. - Inside a rule, use `ensure` for **rules** and `run` for **scripts only** — not `prompt`, `send`, or `run async`. - Treat rules as non-mutating checks; perform filesystem or agent mutations in **workflows**. Script steps from rules use the same managed subprocess path as workflows. Details: [Sandboxing](sandboxing.md). - **Parallelism:** `run async ref([args...])` for managed async with implicit join. For concurrent **bash**, use `&` and the shell builtin `wait` inside a **`script`** and call it with `run`. Do not call Jaiph internals from background subprocesses unless you understand how isolation and logging interact with the runtime. - **Shell conditions:** Express conditionals with `run` to a **script** and handle failure with `catch`, or use `if` / `match` for value branching. Short-circuit brace groups remain valid **inside `script`** bodies: `cmd || { ... }`. - **No shell redirection around managed calls:** `run foo() > file`, `run foo() | cmd`, `run foo() &` are all `E_PARSE` errors — shell operators (`>`, `>>`, `|`, `&`) are not supported adjacent to `run` or `ensure` steps. Move shell pipelines and redirections into a **`script`** block and call it with `run`. - **Script reuse:** Prefer `import script "./tool.py" as tool` (or a sibling `.jh` module) instead of maintaining ad-hoc bash outside the compiler. Avoid informal workspace-level shared-bash directories that bypass the module graph. - **Unified namespace:** Channels, rules, workflows, scripts, script import aliases, and module-scoped `local`/`const` share a single namespace per module (`E_PARSE` on collision). - **Calling conventions (compiler-enforced):** `ensure` must target a rule — using it on a workflow or script is `E_VALIDATE`. `run` in a **workflow** must target a workflow or script; `run` in a **rule** must target a **script** only. **Type crossing:** `string` and `script` are distinct primitive types — `prompt` rejects script names, `run` rejects string consts, assigning a script to a `const` or interpolating `${scriptName}` are all `E_VALIDATE`. See [Grammar — Types](grammar.md#types). Jaiph symbols must not appear inside `$(...)` in bash contexts the compiler still scans (principally **`script`** bodies). Script bodies cannot contain `run`, `ensure`, `config`, nested definitions, routes, or Jaiph `fail` / `const` / `log` / `logerr` / `return "…"`. ## Authoring Heuristics - Keep workflows short and explicit. - Put expensive checks after fast checks. - Include clear prompts with concrete acceptance criteria. - Reuse rules via `ensure`; reuse workflows and scripts via `run`. - **Always run `jaiph format` on `.jh` files you create or modify before committing.** This ensures canonical whitespace, indentation, and top-level ordering. In CI, use `jaiph format --check` to gate on formatting. - Use only syntax described in [jaiph.org](https://jaiph.org) and [Grammar](grammar.md). For advanced constructs (e.g. `config` block, `export`, prompt capture), see the grammar. For testing workflows, see [Testing](testing.md) (`expect_contain`, `expect_not_contain`, `expect_equal`, mocks). ## Writing Tests Test files use the `*.test.jh` suffix and contain `test "name" { ... }` blocks. They import the workflows under test and use mocks to replace live agent/script behavior. The test runner uses the same `NodeWorkflowRuntime` as `jaiph run`. See [Testing](testing.md) for the full reference. **Running:** `jaiph test` (all `*.test.jh` in workspace), `jaiph test ` (recursive), or `jaiph test ` (single file). **Available mocks:** - `mock prompt "fixed response"` — queues a fixed response for the next `prompt` call (multiple queue in order). - `mock prompt responseVar` — uses the string already bound as `responseVar` (e.g. a `const` earlier in the block) as the next response. - `mock prompt { /pattern/ => "response", _ => "default" }` — content-based dispatch. - `mock workflow alias.name() { return "stubbed" }` — replaces a workflow body. - `mock rule alias.name() { return "ok" }` — replaces a rule body. - `mock script alias.name() { … }` — replaces a script body with **shell lines** between the braces (same line as `{` is not enough; put the shell on the following lines, then `}` on its own line). **Assertions:** - `expect_contain var "expected substring"` - `expect_not_contain var "unwanted text"` - `expect_equal var "exact expected value"` **Minimal example:** ```jaiph import "main.jh" as app test "happy path produces greeting" { mock prompt "hello from mock" const out = run app.default("task") expect_contain out "hello from mock" } test "handles failure gracefully" { mock prompt "error" const out = run app.default("bad input") allow_failure expect_contain out "error" } ``` `allow_failure` on a `run` step (with or without `const … =`) prevents a non-zero workflow exit from failing the test — useful for testing error paths. For **`mock script`**, put shell lines on lines after the opening `{`, then close with `}` on its own line (see [Testing](testing.md)). ## Suggested Starter Layout - `.jaiph/bootstrap.jh` — Created by `jaiph init`; contains a single triple-quoted prompt (`prompt """ ... """`) that points the agent at `.jaiph/SKILL.md` (a copy of this guide). - `.jaiph/readiness.jh` — Preflight: rules and `workflow default` that runs readiness checks. - `.jaiph/ba_review.jh` (or any name you choose) — (Optional) Pre-implementation review: reads tasks from a queue file, sends one to an agent for review, and marks it dev-ready or exits with questions. This repository uses `.jaiph/architect_review.jh` with `QUEUE.md`. - `.jaiph/verification.jh` — Verification: rules and `workflow default` for lint/test/build. - `.jaiph/main.jh` — Imports readiness, optional review, and verification; defines implementation workflow and `workflow default` that orchestrates preflight → (optional) review → implementation → verification. Optional: `.jaiph/implementation.jh` if you prefer the implementation workflow in a separate module; otherwise keep it in `main.jh`. ## Final Output Requirement After scaffolding workflows, print the exact commands the developer should run. The primary command runs the default entrypoint (typically preflight, then implementation, then verification — plus any optional review step you added). Point users to the canonical skill URL for agents: . Include a compile check and, when the repository has native tests (`*.test.jh`), `jaiph test` (see [Testing](testing.md)); skip `jaiph test` if there are no test files, since discovery mode exits with an error when nothing matches. ```bash jaiph format .jaiph/*.jh jaiph compile .jaiph # Omit the next line when the repo has no *.test.jh files (workspace discovery exits 1 with "no *.test.jh files found"). jaiph test jaiph run .jaiph/main.jh "implement feature X" # Or run verification only: jaiph run .jaiph/verification.jh ``` Arguments after the file path are passed to `workflow default` as named parameters (when declared) and as `$1`, `$2` in script bodies. ## Minimal Sample (Agent Reference) Use this as a shape to adapt. Paths and prompts should match the target repository. All three files live under `.jaiph/`. Imports in `main.jh` are relative to that file (e.g. `"readiness.jh"` resolves to `.jaiph/readiness.jh`). When you run `jaiph run .jaiph/main.jh "implement feature X"`, the default workflow receives `"implement feature X"` as `${task}` (named parameter). Note that `run` does not forward args implicitly, so the default workflow passes `task` as a bare identifier to `run implement(task)` so the implement workflow's prompt can use `${task}`. **File: .jaiph/readiness.jh** ```jaiph script git_is_clean = `test -z "$(git status --porcelain)"` rule git_clean() { run git_is_clean() catch (err) { fail "git working tree is not clean" } } script require_git_node_npm = ``` command -v git command -v node command -v npm ``` rule required_tools() { run require_git_node_npm() } workflow default() { ensure required_tools() ensure git_clean() } ``` **File: .jaiph/verification.jh** ```jaiph script npm_test_ci = `npm test` rule unit_tests_pass() { run npm_test_ci() } script run_build = `npm run build` rule build_passes() { run run_build() } workflow default() { ensure unit_tests_pass() ensure build_passes() } ``` **File: .jaiph/main.jh** ```jaiph import "readiness.jh" as readiness import "verification.jh" as verification workflow implement(task) { prompt """ Implement the requested feature or fix with minimal, reviewable changes. Keep edits consistent with existing architecture and style. Add or update tests for behavior changes. User asks for: ${task} """ } workflow default(task) { run readiness.default() run implement(task) run verification.default() } ```