Policy packs (YAML)
Manage policies as YAML files alongside your code. Apply them to a project idempotently — perfect for CI / GitOps.
The dashboard is the right place to author policies interactively. The YAML pack format is the right place to keep them once you want them version-controlled, code-reviewed, and applied via CI.
Four packs ship in the repo at policies/:
| File | Posture |
|---|---|
dev-safe.yaml | Workspace agents — destructive shell tools blocked, network allowlist for github/npm, no secret-file reads. |
finance-agent.yaml | Hard cap at $10k USD, mid-range transfers gated by HITL approval, audit CC on outbound email, ledger mutations refused. |
agent-sandbox.yaml | Default-deny — short whitelist of read-only tools, shadow surveillance on email, catch-all deny. |
hitl-and-steer.yaml | Demonstrates approve (HITL) and steer (redirect) effects: hold prod DB writes for review, redirect prod writes to dev in staging. |
Treat them as templates. Copy and edit the one closest to your shape.
Format
policies:
- name: finance / hard-cap-10k-usd
description: Refuse any USD transfer above 10,000.
enabled: true # default true
enforcement_mode: enforce # enforce | shadow
effect: deny # allow | deny | modify | approve | steer
conditions:
- field: tool
operator: equals
value: transfer_funds
- field: input.currency
operator: equals
value: USD
- field: input.amount
operator: greater_than
value: 10000
# only on effect: modify
modifications:
- field: input.cc
value: [email protected]
# only on effect: steer
redirect_to:
tool: send_email_via_audit
input:
audited: trueField-by-field:
name— natural key inside the project. The loader uses it for idempotent upserts; pick stable, namespaced names (finance / hard-cap-10k-usd).description— optional, surfaces in the dashboard.enabled— boolean, defaulttrue.enforcement_mode—enforceblocks/modifies for real;shadowrecords the decision without affecting the call. See policy-engine.effect— what the engine returns when the policy matches.allow/deny— terminal decisions.modify— overwrite input fields before executing (requiresmodifications).approve— park the action inpending_actionsfor human review (HITL).steer— redirect to a different tool (requiresredirect_to).
conditions— non-empty array; all must match (AND).modifications— required wheneffect: modify, ignored otherwise.redirect_to— required wheneffect: steer.{ tool, input }is the substitute call the SDK runs in place of the original.
Where the packs live
The curated starter packs (finance-agent.yaml, dev-safe.yaml,
agent-sandbox.yaml, hitl-and-steer.yaml) live in the public
klentlabs/klent-sdk-ts
repo under policies/. Browse them on GitHub, fork into your own infra
repo if you want them versioned alongside your code, or copy individual
policies straight into the dashboard.
Apply a pack — dashboard (recommended)
Easiest path for one-offs and for tweaking before applying:
- Open the pack on GitHub (e.g.
finance-agent.yaml). - Copy one policy's YAML block at a time.
- In
app.klent.dev→ Policies → New policy → paste-YAML mode (top-right of the form). Repeat for each policy in the pack.
The form parses the YAML, validates against the policy schema, and
upserts on name — re-pasting an edited policy updates it in place
rather than creating a duplicate.
Apply a pack — curl + yq (for CI)
The API's POST /v1/policies endpoint takes one policy at a time as
JSON. To apply a YAML pack, split it into per-policy JSON and POST each
one. Requires yq and jq:
PACK=finance-agent
curl -sf "https://raw.githubusercontent.com/klentlabs/klent-sdk-ts/main/policies/${PACK}.yaml" \
| yq -o json '.policies[]' \
| jq -c \
| while IFS= read -r policy; do
curl -fsS -X POST https://api.klent.dev/v1/policies \
-H "Authorization: Bearer $KLENT_API_KEY" \
-H "Content-Type: application/json" \
-d "$policy"
doneThe API key already scopes the request to one project — no project_id
in the URL. Each successful POST returns a pol_… ID.
Heads-up — the bare API is not idempotent. Re-running the loop against an unchanged pack will fail with a duplicate-name conflict on the second attempt. For idempotent automation, write a thin wrapper that
GET /v1/policies, diffs against the pack, andPATCHes matches /POSTs misses — or use the dashboard's paste flow, which does the upsert for you. A native YAML import endpoint with built-in upsert/diff is on the roadmap.
In CI
For automation that needs to converge a project to the pack on every deploy, the simplest pattern is "delete then re-create" if the project is dedicated to this repo:
- name: Apply policy packs
run: |
for pack in finance-agent dev-safe; do
curl -sf "https://raw.githubusercontent.com/klentlabs/klent-sdk-ts/main/policies/${pack}.yaml" \
| yq -o json '.policies[]' \
| jq -c \
| while IFS= read -r policy; do
curl -fsS -X POST "https://api.klent.dev/v1/policies" \
-H "Authorization: Bearer ${KLENT_API_KEY}" \
-H "Content-Type: application/json" \
-d "$policy"
done
done
env:
KLENT_API_KEY: ${{ secrets.KLENT_API_KEY }}Combine with DELETE /v1/policies/:id on a GET /v1/policies listing
in a pre-step if you want a fully declarative state on every run.
Authoring tips
- Allow before deny. First-match-wins evaluation means narrow allows short-circuit broad denies. Put your allowlist at the top.
- Use
shadowfor new rules. Land the policy asenforcement_mode: shadow, let the timeline collect "would have blocked" matches for a few days, then flip toenforcewhen the rate looks right. - Name policies hierarchically.
finance / cc-audit-on-emailis searchable and groups by namespace in the dashboard list. - Keep packs small. A 50-line YAML is reviewable; a 500-line one isn't.
Split by team or risk surface (
finance.yaml,network.yaml,pii.yaml).