KilnDocs
Factory configuration

Policies and budgets

Protect paths from self-improvement, require two approvals on sensitive files, and cap what a work item may spend.

Policies in policies/<name>.yaml set rules the factory cannot change on its own, and budgets in factory.yaml cap what each work item may spend.

A policy file

# policies/guard.yaml
description: Keep the factory from weakening its own checks
protectedPaths: [policies/**, gates/**, decisions/command-guard.yaml]
twoApprovalPaths: [agents/review/**]

A factory can have several policy files. Kiln combines the lists from all of them.

Key Enforced today What it does
protectedPaths Yes Self-improvement drops any proposed change to a matching path and lists it in the proposal.
twoApprovalPaths Yes A self-improvement proposal that changes a matching path needs approvals from two different people before Kiln applies it.
deniedActions Not yet Accepted by the schema.
allowedTriggerers Not yet Accepted by the schema.
maxUsdPerWorkItem Yes A spending cap per work item. See Budgets.
description Not applicable

Patterns are paths relative to the definition root. * matches within one directory level and ** matches across levels.

Self-improvement never changes files under policies/ or gates/, whether or not a policy lists them.

Rules that do not need a policy

Some limits are built into Kiln and cannot be turned off:

  • A run token can push only kiln/* branches of the factory's repositories, open pull requests, comment and add labels. It cannot approve or merge, and it expires after 60 minutes.
  • Kiln pushes the agent's branch itself. The agent is told not to push, open pull requests or merge.
  • A work item can only become complete after a merge event or a person's confirmation.
  • Agents cannot approve self-improvement proposals.

Budgets

# factory.yaml
budgets:
  perWorkItemUsd: 40
  monthlyUsd: 6000
Key Enforced today What it does
perWorkItemUsd Yes The default budget of each new work item.
monthlyUsd Yes The most the factory may spend on runs in a calendar month.

Before each agent run, Kiln checks two limits:

  • Per work item. The cap is the lowest of the work item's budget and every policy's maxUsdPerWorkItem. A work item's budget is perWorkItemUsd, unless the work item was created through the API with its own budget, which replaces it. When the item's spend has reached the cap, it stops as blocked with the reason "budget of $40 reached ($41.20 spent)".
  • Per month. Kiln adds up the cost of all the factory's runs since the 1st of the month (UTC). When that has reached monthlyUsd, the item stops as blocked with the reason "monthly budget of $6000 for webapp reached ($6012.40 spent)".

A pipeline's budget.maxUsd is accepted but not enforced yet.

Cost comes from each run's harness. Claude Code and OpenCode report cost directly. Codex reports tokens, and Kiln estimates the cost from them.

Kiln also checks while a run goes. Each time the harness reports cost, Kiln saves it on the run and checks the same limits again, plus the team's spend limit. The work item check counts the item's finished runs and its runs still going. When a limit is reached, Kiln stops the agent and the run ends as failed with budget_exceeded. Cost the harness reported stays on the run, even when the run fails afterwards.

Codex and OpenCode report cost after each turn, so Kiln can stop them during a run. Claude Code reports cost when its session ends, so a Claude Code run can still take spend past a budget.

Who may decide

Who may approve a gate is set by the gate's approvers (see Pipelines and gates) and by the scopes on the person's token (see Admin, RBAC and audit).

Both must hold. A person needs the gate's decide:* scope on the factory, and must be one of the gate's assignees, on every surface: web, API, CLI, MCP, Slack, Discord and Teams. A gate without assignees can be decided by anyone with the scope. A token with admin on the factory may decide any of its gates. Anyone else gets HTTP 403, and a message from them in the task's thread joins the conversation instead of answering an open question.

Edit this page on GitHub