Skip to content

Latest commit

 

History

History
591 lines (451 loc) · 36.5 KB

File metadata and controls

591 lines (451 loc) · 36.5 KB

Tech Leads Club

npm version total downloads monthly downloads license build status

bun node version typescript npm provenance zero runtime dependencies

cursor claude code platforms

github stars contributors last commit

🛡️ Harness Toolkit

Steers Cursor and Claude Code agents with gates → follow-up → handoff → policy

Hooks fire on the editor's own events. The harness answers each one with a decision — allow, ask, deny, or text injected into the turn — and writes a record of what it decided and why. 7 floor rules no configuration can reach, 3 always-on checks, and 24 rails you choose.

Start here

npm i -g @tech-leads-club/harness-toolkit && tlc harness install

Then restart Cursor or Claude Code. That is the whole setup — install finds which of the two you have and wires only those, and the harness works in every repository right away with a safe baseline.

To give one project its own rules, open it and say "setup harness" to the agent, or run tlc harness init --minimal. To check anything, run tlc harness doctor.

Table of contents

  1. Start here
  2. Everything it validates
  3. How to see any of it
  4. How to explain a decision
  5. Providers
  6. Requirements
  7. Install
  8. Update
  9. Quick start
  10. How it works
  11. Commands
  12. Connect a project
  13. Paths and shared state
  14. Ship claims
  15. Prices
  16. Troubleshooting
  17. Documentation
  18. Contributing
  19. License

Everything it validates

Three tiers, and which tier a check is in decides whether you can turn it off.

Tier Count Configurable Runs
Floor 7 rules Never Before any policy is loaded, on every tool call, shell command and read
Always on 3 checks Never After the floor, on every acting event
Rails 24 capabilities Each one, individually Where the table says

Nothing else runs. If a message on your screen is not from one of the thirty-four rows below, it is not the harness.

Tier 1 — the floor, no configuration reaches it

Evaluated before the policy file is read, so no setting and no edit by an agent can clear one. Every denial prints rule=<name>, and the name is the first column here.

Rule Denies Allowed anyway
outside-project-destruction a destructive command whose target resolves outside the repository and outside the OS temp directory the same command inside the repository, or inside the temp directory
unprovable-destruction a destructive verb whose target is a variable, a command substitution, or otherwise built at runtime — the harness cannot see what it would delete a literal path it can resolve and check
secret-access a read that would copy a credential into the transcript — .env, ~/.ssh, ~/.aws, *.pem and similar through a shell reader or the editor's own read tool, and the instance metadata service through any verb that speaks to the network searching local files for the literal address, because grep and its kin make no request
history-rewrite git push --force --force-with-lease, which refuses on its own when the remote moved
machine-control shutdown, reboot, halt, poweroff
unprovable-execution a program fetched over the network and handed to a shell — piped, process-substituted, or inside a shell's -c/eval substitution. The gate cannot read what would run a fetch with no shell downstream, and a shell fed a local file the gate can read
policy-surface-write every route an agent has to harness policy and state — a shell redirect, an interpreter, a heredoc program, or a write tool — in the project and under the runtime home, plus the mutating tlc harness subcommands from inside a session reading them with a proven reader (cat, head, grep, jq, ls, stat, test), and tlc harness handoff for the handoff state

Policy changes are the operator's, from a terminal outside the agent session:

tlc harness gate test-command node --test 'src/**/__test__/*.test.ts'
tlc harness gate lint-command npx biome check .

Tier 2 — always on, no switch

Not floor rules, and equally unconfigurable — each one detects a condition that a config field could otherwise switch off.

Check Fires on Verdict What it checks How to see it
policy-baseline-divergence every acting event deny Every policy source is hashed at session start. If one changes mid-session with no tlc harness command behind it, the next acting call is refused and the path named. Reads still pass, so the agent can investigate and report tlc harness policy lists what changed; tlc harness policy accept <path> clears it
policy-surface-write (tool half) tool.before deny An agent write to policy or state through Write, Edit, Delete, MultiEdit or NotebookEdit — the same paths the floor's shell half covers tlc harness obs report — refusals by rule
edit-collision tool.before ask Another live session in the same working tree touched this file recently tlc harness status lists the live sessions

Tier 3 — the rails you choose

All 24 are off unless the default column says on, and each was presented with its benefit and its trade-off when you ran the init wizard. configPath is the key in .tlc/harness/config.json.

Rail · key · default What it checks Fires on Verdict How to see it
Grind (lint/test on stop)
grind.enabled · off
Runs your lint and test commands against the files the turn changed, and sends the agent back until they pass. stop follow-up tlc harness obs report — runs, wall-clock and total; the last verdict is in the project state directory as last-gate.json
Ship gate
shipGate.enabled · off
Checks a declared ship claim against recent PASS evidence for the runtime paths the turn touched. stop block-stop tlc harness obs report; the ship ledger in the project state directory records every claim, challenge and pass
Empty-diff anti-ship
shipGate.emptyDiffAntiShip · off
Checks that a ship claim has a non-empty diff behind it. stop block-stop the ship ledger in the project state directory — the challenge row names the empty diff
Comment gate (agent-added comments)
comments.enabled · off
Checks the comment lines this turn added against the commit the turn started from: by reason, by resolvability, or none. stop block-stop tlc harness obs report — the comments gate appears among the gate outcomes
Duplication gate (agent-added copies)
duplication.enabled · off
Checks whether the runs of code this turn added already exist somewhere else in the project. stop block-stop tlc harness obs report — the duplication gate appears among the gate outcomes
Supply-chain gate (dependencies this turn added)
supplyChain.enabled · off
Checks what this turn added to the dependency graph: a manifest that moved without its lock, or an unpinned version. stop block-stop tlc harness obs report — the supply-chain gate appears among the gate outcomes
Subagent allowlist
subagents.enforceAllowlist · off
Checks a subagent's model against the list you wrote, and against the blocked *-fast shapes. tool.before
subagent.start
deny tlc harness obs report — refusals attributed by rule; the denial text names subagents.allowedModels and lists what is permitted
Block parent Fast mode for Task spawns
subagents.blockParentFast · off
Checks whether the parent chat is in Fast mode before letting it spawn a subagent. tool.before
subagent.start
deny tlc harness obs report — refusals by rule; tlc harness status shows the sticky parent model it read
Shell stall detection
shell.stallDetection · off
Counts identical shell commands in a row and stops the loop at your threshold. shell.before deny tlc harness obs report — interruptions attributed to the shell-stall rule
Catastrophic shell ask
shell.catastrophicAsk · on
Checks a shell command for destruction that reaches outside the workspace. shell.before ask tlc harness obs report — interruptions attributed to the shell-catastrophic rule
Lessons
intelligence.lessons.enabled · off
Records what a repeated gate failure taught, ranks it, and injects it into the next session and retry. session.start
stop
session.end
context tlc harness lessons list — every tier with staleness and effectiveness; obs report shows the characters each injection cost
Budget continue
intelligence.budgetContinue · off
Checks for unfinished handoff work under context pressure and says keep going rather than wrap up. stop follow-up tlc harness handoff — the follow-up fires only with unfinished work recorded there
Gap feedback
intelligence.gapFeedback · on
Turns a gate's output into a numbered list of gaps the retry has to close. stop follow-up tlc harness handoff — the gaps it injects are the ones stored as previous_gaps
Failure classification
intelligence.failureClassification · on
Labels each gate failure with a category and stores it on the handoff. stop record tlc harness handoff — last_failure_category
Progressive handoff
intelligence.progressiveHandoff · on
Reads the gaps the previous session ended with back out at the next session's start. session.start context tlc harness handoff — the gaps it reads back out are previous_gaps
Progressive context
intelligence.progressiveContext · on
Raises the detail in the follow-up on each stop retry, so a repeat attempt is not given the same prompt. stop follow-up tlc harness obs report — the retry count for a stop is the escalation level it reached
Autopilot
intelligence.autopilot · on
Emits ordered steps after a gate failure, computed by the runtime rather than invented by the model. stop follow-up the AUTOPILOT block is in the follow-up text itself; obs report counts the failing stops that produced one
Idle-turn gate (asked instead of acting)
intelligence.idleTurnGate · off
Checks whether a turn that ended with open work recorded any tool call or file change at all. stop block-stop tlc harness obs report for the block; tlc harness handoff shows the open work that armed it
Docs staleness gate
docs.command · off
Runs the repository's own documentation staleness tool on stop, like a lint command. stop block-stop tlc harness obs report; the docs gate writes the same last-gate.json artifact the lint and test gates do
Global observability spool
obs.globalSpool · off
Copies every record into one file under the runtime home, so cost is readable across repositories. tool.after
tool.failure
record the spool file under the runtime home; tlc harness obs prune reports how many records it dropped
Untrusted-content framing and enforcement
untrustedContent.enabled · off
Frames outside content as data, and in enforce mode asks before a command that appears verbatim in it. tool.after context tlc harness obs report — one framing injection per turn, with the characters it cost
Plan gate (declared scope vs diff)
planGate.enabled · off
Checks the files the turn changed against the scope it declared, and against any stated deviation. response.after
stop
block-stop tlc harness handoff — plan_paths, plan_at and plan_deviations
Observation mode (measure a rail with its rule off)
observe.enabled · off
Runs a rail's checker while that rail is not enforcing, and records the reading without acting on it. stop
session.end
record tlc harness obs report — the observation readings, held apart from the refusal counters so those stay honest
Operator rules (your trigger, your proof)
rules.enabled · off
Reads the rules you declare in markdown and enforces them: on this trigger, this must have happened. tool.before
stop
deny tlc harness doctor — each active rule, its tier, and any proof kind never observed here

The two tables above are generated from capabilities/catalog.json and src/core/floor/floor.catalog.ts. tlc harness test fails when they drift, so a rail that exists and is not listed here is a build failure rather than a documentation gap.

Each rail's full benefit and trade-off — the long form, as the init wizard reads them out — is in docs/architecture.md and docs/concepts.md.

One thing is not in any table: operator posture. tlc harness mode paired|solo|focus changes how much the agent surfaces and what earns an interruption. It switches no gate on and weakens no verification — the evidence bar is identical at all three (docs/decisions/ad-025.md).

One rail takes a rule you write

Every other capability is a switch. Operator rules is a switch plus a file, because the trigger and the proof are yours: on this trigger, this must have happened, otherwise do that. No pull request without a review, for instance:

---
on: pr-open
require:
  - subagent(the-jury) since HEAD
otherwise: deny
---

Convene the jury on this branch. Checklist: docs/review-checklist.md

Save it as one markdown file per rule in .tlc/harness/rules/ for this repository, or in the runtime home's rules/ for every repository on this machine, and set rules.enabled: true. The proof has to be something the harness observed — a subagent that ran, a command that completed, a gate that passed, a file that changed — so the agent cannot write one: that store is under the project state directory, which the floor refuses it.

tlc harness doctor lists every rule it loaded, and says so when the switch is on and no file was found. The full grammar — six triggers, four proof kinds, four verdicts and how each lands at the stop — is tlc harness help rules.

Why not the editor's own rules? Because a rule file there is context, not a gate: activation is a mode you pick, always-apply defaults to off, and one of the modes leaves it to the model to decide whether to load the rule at all. This is not a replacement for that file — it is the layer underneath it. The editor's rule says what the agent should do; this one says what must be provably true before it may.

The fair comparison is with the editors' hooks, which are deterministic too. Three things separate them from this, taken from their own reference rather than from opinion: there is no prerequisite mechanism and no state store, so "X requires Y to have happened" is custom logic you write per event; the state you would keep lives in an ordinary repository path the agent can write, while a proof here lives where the floor refuses it; and one host's stop hook cannot refuse completion at all, so the same requirement has to mean something different on each — which is what the capability table and the degrade step already do. A hook is a program per host per repository, fail-open unless it opts out. A rule is five lines of frontmatter, two tiers, both hosts (docs/decisions/ad-100.md).

How to see any of it

Every row above names a command in its last column. These are those commands.

Command Answers
tlc harness status Which posture, which rails are on, which sessions are live, whether policy diverged
tlc harness doctor Whether the install is healthy, and every rail that is off or misconfigured — including a rail switched on with nothing to enforce
tlc harness obs report Per session: gate outcomes, refusals attributed by rule, interruptions by rule, characters injected, cost
tlc harness obs live The same signal as it happens
tlc harness handoff What the turn left open: gaps, blockers, next action, plan scope, failure category
tlc harness lessons list Every lesson in all three tiers, with staleness, validity and whether it ever helped
tlc harness why [n] The last n decisions this tool made, with the rule behind each — and a plain sentence when it made none
tlc harness attest One hash-chained record per session: policy in force, rails active, refusals by rule, gate outcomes
tlc harness policy Which policy source changed mid-session, changing nothing
--json on any of them The same content, machine-readable

All of these read. None of them changes a decision.

How to explain a decision

Start with tlc harness why — the last ten decisions this tool made, each with the rule behind it. When the harness did nothing, it says that in words, which is the answer no other command gives. Full guide: docs/troubleshooting.md.

You saw a message and want to know which rule produced it.

  1. The message names its rule. A floor denial ends in rule=<name> — look it up in tier 1. A rail's block names the gate.
  2. tlc harness obs report attributes every refusal and interruption in the session to a rule, so "seven interruptions" becomes "six from the posture, one from the catastrophic rule".
  3. tlc harness attest is the same thing for a reviewer: which policy the session ran under, whether it changed mid-session, and every gate outcome, hash-chained so a removed record is detectable.
  4. tlc harness doctor explains the absence of a decision — a rail you expected to fire and did not is usually one that is off, or on with nothing configured to enforce.

Two limits worth stating. The harness records the decisions it made; it never learns your answer to an ask, so it reports a rate and its attribution, never a precision or an accuracy. And the attestation is chained, not signed: it detects a rewritten record and proves nothing about authorship.

What it covers, and what it does not

docs/coverage.md assesses the harness against a published agentic-risk taxonomy — four risks covered, five partial, one not applicable — and states what each row leaves open. It is a self-assessment, and the control names in it are generated from the same catalogs this README's tables come from, so a rail that is renamed or removed fails the build rather than leaving a claim standing.

Providers

Both providers share one runtime, one project policy file, and one on-disk state directory. Core steering logic never imports a provider adapter and never branches on a provider's name — see docs/architecture.md and docs/providers/index.md.

Provider Detected by User-level wiring Docs
Cursor CURSOR_CONFIG_DIR, else ~/.cursor <resolved>/hooks.json (replaced) docs/providers/cursor.md
Claude Code CLAUDE_CONFIG_DIR, else ~/.claude <resolved>/settings.json hooks block (merged) docs/providers/claude-code.md

The installer and tlc harness init detect which of these are present and wire only those — neither assumes Cursor.

A rail fires only where the provider can express it. ask on an event a provider cannot ask about becomes deny, and injected context on an event a provider ignores is withheld rather than rendered into a field nothing reads — src/providers/provider.degrade.ts.

Requirements

Dependency Notes
Bun or Node.js 24+ Either one is enough. Bun runs every hook directly (~1 ms/hook); Node runs the shipped bundles (~27 ms/hook). With neither, the install stops and names both fixes
npm Delivers the package, and generates the tlc shim for the platform it runs on

Linux, macOS and Windows, same commands and same code path. CI runs the full suite and the bundle build on all three on every push; hooks firing inside an editor session on Windows are outside that coverage (docs/decisions/ad-006.md, docs/decisions/ad-097.md).

Install

One command, the same on every platform.

npm i -g @tech-leads-club/harness-toolkit && tlc harness install

The package is the delivery vehicle and tlc harness install is what puts the runtime in place. They are separate because a directory npm replaces wholesale cannot hold state — config.json, state/ and flags/ would be deleted by an ordinary update (docs/decisions/ad-056.md).

Install target: ~/.tlc/harness (runtime). The init skill is linked into the skills directory of each provider it finds, because a provider only reads its own.

tlc harness install:

  1. Materialises the runtime at ~/.tlc/harness from the installed package
  2. Creates config.json from config.example.json when missing
  3. Links tlc into ~/.local/bin, and says so if that directory is not on PATH
  4. Links the init skill into each detected provider's skills/harness-init
  5. Wires user-level hooks for every provider it detects installed, in that provider's resolved config directory

Update with tlc harness update, which bumps the package and re-materialises the runtime.

Every release is published from CI with a short-lived OIDC credential — no token is stored in the repository — and carries a provenance attestation you can check on npmjs.com. What gates it is the test suite on four platforms plus an install of the packed tarball into a clean container, driven as a real command (docs/decisions/ad-102.md).

Overrides: TLC_HOME, TLC_REPO_URL, TLC_BIN_DIR.

Running a development copy

The runtime that answers a hook is whichever one TLC_HOME names, and it wins over the location of the launcher that was invoked. So a checkout can be exercised without touching the machine's install:

# one command against the checkout
TLC_HOME=/path/to/clone tlc harness doctor

# a whole editor session on the checkout — everything else on the machine stays on the installed copy
export TLC_HOME=/path/to/clone
cursor .

Two things worth knowing before doing that:

  • A checkout serves its hooks from source. With Bun present the launcher runs src/ directly, so a file saved half-edited is live on the next hook — in that session only. Nothing needs building first, and nothing protects you from a syntax error either.
  • Do not install a clone with --link as the machine's runtime unless you accept that your working tree is what every session on the machine runs. That is the contributor route, and it is why doctor reports it distinctly (runtime ownership — link to a working clone).

Neither of those applies to the ordinary route: tlc harness test runs the whole gate hermetically, and the entrypoints can be driven with real hook payloads from a scratch directory, so most work needs no live session at all.

Provider config directories are resolved, not assumed: CLAUDE_CONFIG_DIR and CURSOR_CONFIG_DIR are honoured when set, so a relocated config is wired correctly. tlc harness doctor prints the resolved target for each provider.

Uninstall

tlc harness uninstall            # print the plan; change nothing
tlc harness uninstall --yes      # apply exactly that plan
tlc harness uninstall --purge --yes   # and delete config.json, state/ and flags/ too

The plan is the confirmation — there is no prompt, so it works the same in CI and in a shell that is already broken. What it does, and what it deliberately does not:

settings.json un-merged, never deleted. Every key and every hook that is not ours stays exactly where it is
hooks.json deleted when every entry was ours, rewritten when it holds somebody else's
the tlc link and the skill links removed
the runtime payload at ~/.tlc/harness removed — unless it is a symlink to a checkout, which is unlinked and never followed
config.json, state/, flags/ kept, unless you add --purge
the global npm package reported with the exact command, never run for you
.tlc/ inside your repositories reported, never hunted for

Running it twice is safe: the second run reports nothing to undo and exits 0.

An agent cannot run this for you. ~/.tlc/harness/state is a policy surface and the runtime home is outside any project, so the floor answers a delegated uninstall with policy-surface-write or outside-project-destruction. That is deliberate — the supervised does not switch off the supervisor.

Restart or reload the provider session after install.

From a git clone — that is a contributor install, and it is described in CONTRIBUTING.md. Update never writes into a clone.

Update

tlc harness update

Moves the runtime to upstream, refreshes CLI + init skill + provider wiring, then runs doctor. Reload/restart the provider session afterward if hooks or the init skill should refresh.

The runtime path is an artifact the harness owns, and update never touches anything else (AD-046):

tlc harness doctor says What update writes
installed from npm bumps the package to @latest and re-materialises the runtime. No git command runs
managed checkout moves it to upstream with a hard reset. Do not develop there — a local change is discarded
link to a working clone nothing in the clone. That is a contributor install; you pull it yourself

The bundles are rebuilt only when one is missing, and only Bun builds them — a second bundler emits different bytes for the same source.

If update aborts on dist/, install the package again. A stuck install cannot deliver its own fix — the fix lives in the revision update has to fetch — so npm i -g @tech-leads-club/harness-toolkit@latest followed by tlc harness install is the recovery route, because the registry serves it independently of what is installed. It leaves config.json, state/ and any linked clone untouched (AD-048). There is no --force.

After a successful pull, prints a short digest of optional catalog capabilities this project has not enabled yet (benefit + trade-off + how to enable). Nothing is auto-enabled — use the harness-init skill or edit .tlc/harness/config.json.

tlc harness doctor emits non-blocking WARN: lines for the same off/missing opt-ins (and for default-on features you explicitly set to false).

Quick start

tlc harness doctor
tlc harness help
tlc harness status

Healthy install checklist:

  • Bun on PATH, or Node 24+ for the dist/ fallback path
  • ~/.tlc/harness present with dist/*.mjs
  • At least one provider's user-level hooks invoke tlc-exec
  • tlc on PATH (open a new shell if needed)

How it works

graph LR
    EV["provider event<br/><i>Cursor or Claude Code hook</i>"]
    HK["user-level hook file"]
    LX["bin/tlc-exec.mjs &lt;handler&gt;<br/><i>Bun first, Node + dist fallback</i>"]
    EP["src/entrypoints/&lt;handler&gt;.ts<br/><i>core + resolved adapter</i>"]
    OUT["follow-up · handoff · observability<br/><i>under project policy</i>"]
    EV --> HK --> LX --> EP --> OUT
Loading
Layer Location
Runtime ~/.tlc/harness
Cursor user hooks <cursor config>/hooks.json
Claude Code user hooks <claude config>/settings.json (hooks block)
Project policy <repo>/.tlc/harness/config.json
Project shim (per provider) <repo>/.cursor/hooks.json, <repo>/.claude/settings.json

Entrypoint: bin/tlc-exec.mjs. Wrappers: bin/tlc and bin/tlc.cmd, kept for installs made before the package existed. Everything else spawns bin/tlc-exec.mjs with the interpreter already running.

See tlc harness help architecture or docs/architecture.md.

Commands

Command Purpose
tlc harness status Mode, grind, gates
--json on any read command Machine-readable output: status, doctor, obs, lessons, prices lookup
tlc harness update Pull runtime + refresh skill/CLI/wiring + doctor
tlc harness doctor Health checklist
tlc harness help [topic] Docs
tlc harness build Compile dist/ for the Node fallback path
tlc harness test Run the full local gate
tlc harness grind [on|off] Lint/test follow-ups on stop
tlc harness pause / resume Disable / enable stop checks
tlc harness mode solo|paired|focus Operator posture
tlc harness attest Tamper-evident record of what each session ran under
tlc harness handoff Handoff state between turns and sessions — the sanctioned reader
tlc harness obs live / obs report Signal / session rollup
tlc harness prices refresh / lookup The machine's price catalogue
tlc harness lessons list Lessons across the three tiers, with staleness and effectiveness
tlc harness lessons add "…" [--ref path:symbol] [--global] [--pin] Write a lesson; --ref retires it when that stops resolving, --pin puts it ahead of ranking
tlc harness policy / policy accept <path> List a mid-session policy change; accept exactly those paths
tlc harness init --minimal A project config that decides nothing; an existing one is kept

Connect a project

  1. Open the repository in Cursor and/or Claude Code.
  2. Run tlc harness init --minimal, or ask the agent to run the harness-init skill.
  3. Confirm .tlc/harness/config.json and the shim hooks for whichever provider(s) you use.
  4. Run tlc harness doctor from the project root.

Details: tlc harness help init or docs/init.md.

Paths and shared state

Both providers read and write the same project state — there is one .tlc/harness/state/, not one per provider. Records inside it (signal, debug, audit) carry a provider field per event.

Path Contents
~/.tlc/harness Runtime
~/.tlc/harness/state/lessons.json Global lesson tier — this machine, every product (AD-040)
<cursor config>/hooks.json Cursor user hooks (if Cursor installed)
<claude config>/settings.json Claude Code user hooks, hooks block (if Claude Code installed)
<provider config>/skills/harness-init Init skill, linked per detected provider from runtime skills/harness-init
<repo>/.tlc/harness/config.json Project policy (tracked)
<repo>/.tlc/harness/state/ Handoff, obs, audit, project-tier lessons.json, ship ledger (gitignored)

Do not use ~/.tlc/harness for anything other than the installed runtime — see docs/decisions/ad-002.md for why the layout is namespaced this way.

Ship claims

Protocol line (free-form "done/shipped" is ignored):

HARNESS_SHIP_CLAIM: <one-line summary>

When shipGate is enabled and runtime paths changed, cite recent PASS under evidenceDir. See tlc harness help concepts or docs/concepts.md.

The plan gate uses the same shape for scope:

HARNESS_PLAN: src/core/plan/**, src/entrypoints/stop.ts
HARNESS_PLAN_DEVIATION: src/x.ts — the call site moved with the type

Both are protocol lines on purpose. A gate that fires on free-English "done" fires on the word, not the claim.

Prices

tlc harness prices refresh
tlc harness prices refresh cursor
tlc harness prices lookup <model-id> [provider]

Catalogue: ~/.tlc/harness/model-prices.json, one plane per billing origin. Overrides: model-prices.local.json. Neither is versioned. install fetches once, update refreshes past a 7-day TTL, doctor reports the age.

See tlc harness help prices or docs/measure.md.

Troubleshooting

Symptom Action
tlc: command not found New shell; ensure ~/.local/bin on PATH; re-run install
Hooks never fire Reload/restart the provider session; check the provider's own hook log; confirm tlc-exec
Missing dist/ tlc harness build
Cost null tlc harness help prices
Project doctor FAILs Expected until project policy exists
A rail you enabled never fires tlc harness doctor — a rail switched on with nothing configured to enforce is reported as a fault
Every tool call is refused after you edited the config tlc harness policy then tlc harness policy accept <path>

See tlc harness help diagnose or docs/diagnose.md.

Documentation

Full OKF v0.1 documentation bundle: docs/index.md.

Contributing

CONTRIBUTING.md · SECURITY.md

License

Elastic License 2.0LICENSE, NOTICE.

Allowed Not allowed
Use, change and redistribute it, inside a company and commercially, at no cost Providing it to third parties as a hosted or managed service
Everything an operator or a team does with it on their own machines and repositories Removing or obscuring the copyright and licence notices

Attribution is a condition of the licence rather than a courtesy: keep NOTICE and the notices in the files with any copy you distribute.

It is source-available, not OSI-approved open source, so a corporate licence scanner will refuse it by default (docs/decisions/ad-105.md).