nextv1.4.0
Guides

Write your own chain

Add a lint node to a copy of the default chain, with an agent that fixes what lint reports, a time cap, and no way to skip it.

This guide builds a chain of your own from the shipped default chain. It adds a lint node after verification. The node runs your linter as a command. When the linter fails, an agent fixes what it reported and the linter runs again. A judge decides when more attempts are not worth it. The node has a time cap, and nobody can skip it.

For every key used here, see Chain nodes and Library and chains.

Before you start

  • A running Kraft server and a connected repo.
  • A lint command that exits 0 when the code is clean. This guide uses npm run lint && npx tsc --noEmit. Use your own.
  • Your config lives in $KRAFT_HOME/templates/ (~/.kraft/templates/ by default). You edit library.yaml and add a file under chains/.

1. Add the tasks to the library

Add two tasks to library.yaml, under tasks:. One runs the linter. The other is the agent that repairs what it reports.

tasks:
  lint:
    kind: subprocess
    command: sh -c 'npm run lint && npx tsc --noEmit'

  repair_lint:
    kind: agent
    harness: claude
    profile: strong
    prompt: Fix the lint and type errors the lint step reported. Change no behavior.

Kraft does not run command through a shell, so && and pipes need the sh -c '...' wrapper above. Inside a fix loop, Kraft replaces the repair task's prompt with its own instruction: which tasks failed and what they reported. See Subprocess tasks and Fix loop and judge.

2. Add the node to the library

Add a lint node to library.yaml, under nodes::

nodes:
  lint:
    kind: exec
    skippable: false
    policy:
      time_cap_minutes: 30
    tasks:
      - id: lint
        extends: lint
    fix_loop:
      tasks:
        - id: repair
          extends: repair_lint
      judge:
        id: judge
        extends: strict_judge
      max_attempts: 3
  • skippable: false refuses kraft item skip on this node, a chain revision that proposes skipping it, and kraft item create --skip-nodes lint.
  • policy.time_cap_minutes: 30 caps the node's running time, fix loop included. Hitting it stops the item for you. It is never treated as a lint failure, so no repair attempt is spent on it. See Time caps.
  • fix_loop.tasks runs after a failing lint, then the lint runs again. The loop repeats until lint passes or max_attempts runs out. Then the node is stuck: an automatic escalation turn tries first, then the item stops for you. The loop's wall clock comes from policy.yaml (default, or loops.lint.fix_loop). See Policy.
  • judge is optional. strict_judge is the shipped judge task, the one verification uses. It runs before each repair cycle after the first, and can stop the loop. Its stop_downgrade verdict never lets a failing lint command through. See The judge.

3. Make the chain

Copy the default chain and give the copy its own id:

cd ~/.kraft/templates
cp chains/default.yaml chains/with-lint.yaml

In chains/with-lint.yaml, change id: default to id: with-lint. Then add the node right after verification:

  - id: verification
    extends: verification

  - id: lint
    extends: lint

A chain file cannot extend another chain, so the copy is a full copy. It no longer follows changes to the shipped default chain. See Upgrading your templates.

4. Check it and load it

kraft admin templates lint
kraft admin templates show with-lint --resolved
kraft admin reload

lint must print no errors before you reload. show --resolved prints the chain with every library component expanded. Look for your lint node with its fix_loop, its policy and skippable: false. reload makes the running server read the files again, with no restart.

5. Use it

Name the chain when you file work:

kraft item create "Add the export button" --chain with-lint

kraft item set-chain ID --template with-lint switches an item that has not started yet.

To make it the repo's default instead, set default_chain_template: with-lint on the repo's entry in repos.yaml. Every item filed on that repo without a chain then gets with-lint: kraft item create, the MCP tool, the board's New work item dialog, POST /api/work-items, POST /api/triggers and auto-intake. Naming a chain still wins.

How the lint node runs

The lint command runs in the item's worktree, with the worker's environment and KRAFT_RESULT_PATH. Exit 0 is done, anything else failed, and a failure becomes a critical finding holding the end of its output for the repair agent. Subprocess tasks has the full contract.

Or use a test scope instead

If you only need lint to run when certain files change, you may not need a node at all. Add the linter to the repo's test_scopes in repos.yaml:

test_scopes:
  - paths: ["src/**"]
    command: npm test
  - paths: ["src/**/*.ts"]
    command: sh -c 'npm run lint && npx tsc --noEmit'

The shipped verification node runs every scope whose paths the change touches, and its existing fix loop repairs the failures. A scope's command is split the same way, with no shell. See Repos.

Verify

File a small item on the new chain. Once it passes verification, watch the lint node run:

kraft view events ID --type findings_measured

A failing lint shows a findings_measured event for node lint with one critical finding, then a fix_cycle_started event. A clean lint shows a findings_measured event with no findings, then node_completed for lint.

kraft item skip ID on the lint node answers path: 'lint' does not allow skipping.

Copyright © 2026