@tech-leads-club/harness-toolkit 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -0
- package/LICENSE +84 -0
- package/NOTICE +9 -0
- package/README.md +480 -0
- package/bin/tlc +10 -0
- package/bin/tlc-build +80 -0
- package/bin/tlc-cli.ts +1509 -0
- package/bin/tlc-exec +10 -0
- package/bin/tlc-exec.cmd +4 -0
- package/bin/tlc-exec.d.mts +60 -0
- package/bin/tlc-exec.mjs +248 -0
- package/bin/tlc.cmd +4 -0
- package/bin/tlc.mjs +7 -0
- package/bin/write-user-hooks.d.mts +40 -0
- package/bin/write-user-hooks.mjs +123 -0
- package/capabilities/catalog.json +418 -0
- package/config.example.json +51 -0
- package/dist/compact-before.mjs +7839 -0
- package/dist/doctor.mjs +8263 -0
- package/dist/help-topic.mjs +82 -0
- package/dist/init-project.mjs +993 -0
- package/dist/install-runtime.mjs +969 -0
- package/dist/lessons-cli.mjs +7258 -0
- package/dist/obs-cli.mjs +7025 -0
- package/dist/price-lookup.mjs +221 -0
- package/dist/prompt-submit.mjs +7860 -0
- package/dist/refresh-model-prices.mjs +158 -0
- package/dist/response-after.mjs +7879 -0
- package/dist/run.mjs +7830 -0
- package/dist/session-end.mjs +7937 -0
- package/dist/session-start.mjs +8056 -0
- package/dist/shim.mjs +52 -0
- package/dist/stop.mjs +8494 -0
- package/dist/subagent-start.mjs +7861 -0
- package/dist/subagent-stop.mjs +7851 -0
- package/dist/support.mjs +7045 -0
- package/dist/tlc-cli.mjs +8063 -0
- package/dist/tool-after.mjs +8113 -0
- package/dist/tool-before.mjs +7985 -0
- package/dist/tool-failure.mjs +7850 -0
- package/dist/uninstall-runtime.mjs +1307 -0
- package/docs/architecture.md +270 -0
- package/docs/concepts.md +562 -0
- package/docs/coverage.md +55 -0
- package/docs/decisions/ad-001.md +32 -0
- package/docs/decisions/ad-002.md +51 -0
- package/docs/decisions/ad-003.md +30 -0
- package/docs/decisions/ad-004.md +37 -0
- package/docs/decisions/ad-005.md +36 -0
- package/docs/decisions/ad-006.md +49 -0
- package/docs/decisions/ad-007.md +36 -0
- package/docs/decisions/ad-008.md +54 -0
- package/docs/decisions/ad-009.md +61 -0
- package/docs/decisions/ad-010.md +45 -0
- package/docs/decisions/ad-011.md +59 -0
- package/docs/decisions/ad-012.md +71 -0
- package/docs/decisions/ad-013.md +87 -0
- package/docs/decisions/ad-014.md +56 -0
- package/docs/decisions/ad-015.md +33 -0
- package/docs/decisions/ad-016.md +98 -0
- package/docs/decisions/ad-017.md +65 -0
- package/docs/decisions/ad-018.md +77 -0
- package/docs/decisions/ad-019.md +75 -0
- package/docs/decisions/ad-020.md +88 -0
- package/docs/decisions/ad-021.md +57 -0
- package/docs/decisions/ad-022.md +120 -0
- package/docs/decisions/ad-023.md +87 -0
- package/docs/decisions/ad-024.md +85 -0
- package/docs/decisions/ad-025.md +92 -0
- package/docs/decisions/ad-026.md +131 -0
- package/docs/decisions/ad-027.md +90 -0
- package/docs/decisions/ad-028.md +90 -0
- package/docs/decisions/ad-029.md +73 -0
- package/docs/decisions/ad-030.md +97 -0
- package/docs/decisions/ad-031.md +94 -0
- package/docs/decisions/ad-032.md +83 -0
- package/docs/decisions/ad-033.md +89 -0
- package/docs/decisions/ad-034.md +86 -0
- package/docs/decisions/ad-035.md +86 -0
- package/docs/decisions/ad-036.md +68 -0
- package/docs/decisions/ad-037.md +47 -0
- package/docs/decisions/ad-038.md +52 -0
- package/docs/decisions/ad-039.md +69 -0
- package/docs/decisions/ad-040.md +89 -0
- package/docs/decisions/ad-041.md +98 -0
- package/docs/decisions/ad-042.md +82 -0
- package/docs/decisions/ad-043.md +79 -0
- package/docs/decisions/ad-044.md +61 -0
- package/docs/decisions/ad-045.md +94 -0
- package/docs/decisions/ad-046.md +111 -0
- package/docs/decisions/ad-047.md +96 -0
- package/docs/decisions/ad-048.md +85 -0
- package/docs/decisions/ad-049.md +66 -0
- package/docs/decisions/ad-050.md +94 -0
- package/docs/decisions/ad-051.md +69 -0
- package/docs/decisions/ad-052.md +69 -0
- package/docs/decisions/ad-053.md +78 -0
- package/docs/decisions/ad-054.md +98 -0
- package/docs/decisions/ad-055.md +74 -0
- package/docs/decisions/ad-056.md +85 -0
- package/docs/decisions/ad-057.md +68 -0
- package/docs/decisions/ad-058.md +97 -0
- package/docs/decisions/ad-059.md +82 -0
- package/docs/decisions/ad-060.md +75 -0
- package/docs/decisions/ad-061.md +68 -0
- package/docs/decisions/ad-062.md +72 -0
- package/docs/decisions/ad-063.md +84 -0
- package/docs/decisions/ad-064.md +79 -0
- package/docs/decisions/ad-065.md +81 -0
- package/docs/decisions/ad-066.md +111 -0
- package/docs/decisions/ad-067.md +64 -0
- package/docs/decisions/ad-068.md +79 -0
- package/docs/decisions/ad-069.md +74 -0
- package/docs/decisions/ad-070.md +86 -0
- package/docs/decisions/ad-071.md +93 -0
- package/docs/decisions/ad-072.md +82 -0
- package/docs/decisions/ad-073.md +102 -0
- package/docs/decisions/ad-074.md +91 -0
- package/docs/decisions/ad-075.md +79 -0
- package/docs/decisions/ad-076.md +102 -0
- package/docs/decisions/ad-077.md +94 -0
- package/docs/decisions/ad-078.md +84 -0
- package/docs/decisions/ad-079.md +73 -0
- package/docs/decisions/ad-080.md +86 -0
- package/docs/decisions/ad-081.md +70 -0
- package/docs/decisions/ad-082.md +79 -0
- package/docs/decisions/ad-083.md +88 -0
- package/docs/decisions/index.md +111 -0
- package/docs/diagnose.md +248 -0
- package/docs/index.md +52 -0
- package/docs/init.md +101 -0
- package/docs/lessons.md +294 -0
- package/docs/log.md +139 -0
- package/docs/measure.md +178 -0
- package/docs/providers/claude-code.md +118 -0
- package/docs/providers/cursor.md +110 -0
- package/docs/providers/index.md +71 -0
- package/docs/troubleshooting.md +138 -0
- package/model-aliases.json +12 -0
- package/model-prices.cursor.json +410 -0
- package/model-prices.json +1 -0
- package/package.json +57 -0
- package/skills/harness-init/SKILL.md +241 -0
- package/skills/harness-init/evals/trigger_evals.json +86 -0
- package/skills/harness-init/references/capabilities.md +113 -0
- package/src/contracts/capabilities.ts +30 -0
- package/src/contracts/decision.ts +21 -0
- package/src/contracts/effort.ts +15 -0
- package/src/contracts/harness-event.ts +87 -0
- package/src/contracts/index.ts +6 -0
- package/src/contracts/policy-defaults.ts +14 -0
- package/src/contracts/wiring.ts +20 -0
- package/src/core/attest/attest.service.ts +0 -0
- package/src/core/capability/capability.service.ts +62 -0
- package/src/core/capability/capability.store.ts +51 -0
- package/src/core/capability/capability.types.ts +56 -0
- package/src/core/comment-policy/comment-policy.service.ts +307 -0
- package/src/core/comment-policy/comment-policy.types.ts +6 -0
- package/src/core/comment-policy/comment-resolvability.ts +134 -0
- package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
- package/src/core/comment-policy/comment-syntax.store.ts +65 -0
- package/src/core/comment-policy/comment-syntax.types.ts +24 -0
- package/src/core/core.facade.ts +461 -0
- package/src/core/duplication/duplication.service.ts +219 -0
- package/src/core/floor/floor.catalog.ts +54 -0
- package/src/core/floor/floor.paths.ts +90 -0
- package/src/core/floor/floor.policy-surface.ts +311 -0
- package/src/core/floor/floor.service.ts +306 -0
- package/src/core/floor/floor.tokenize.ts +179 -0
- package/src/core/floor/floor.verb.ts +28 -0
- package/src/core/floor/index.ts +1 -0
- package/src/core/gate/gate.artifact.ts +157 -0
- package/src/core/gate/gate.command.ts +130 -0
- package/src/core/gate/gate.findings.ts +201 -0
- package/src/core/gate/gate.inputs.ts +0 -0
- package/src/core/gate/gate.lock.ts +283 -0
- package/src/core/gate/gate.service.ts +28 -0
- package/src/core/gate/gate.types.ts +67 -0
- package/src/core/handoff/handoff.service.ts +43 -0
- package/src/core/handoff/handoff.store.ts +59 -0
- package/src/core/handoff/handoff.types.ts +89 -0
- package/src/core/index.ts +52 -0
- package/src/core/integrity/state-seal.ts +81 -0
- package/src/core/lesson/lesson.authored.ts +96 -0
- package/src/core/lesson/lesson.credit.ts +51 -0
- package/src/core/lesson/lesson.garden.ts +283 -0
- package/src/core/lesson/lesson.link.ts +93 -0
- package/src/core/lesson/lesson.score.ts +61 -0
- package/src/core/lesson/lesson.select.ts +209 -0
- package/src/core/lesson/lesson.service.ts +110 -0
- package/src/core/lesson/lesson.store.ts +317 -0
- package/src/core/lesson/lesson.sync.ts +70 -0
- package/src/core/lesson/lesson.types.ts +92 -0
- package/src/core/lesson/lesson.validity.ts +44 -0
- package/src/core/observability/observability.report.ts +326 -0
- package/src/core/observability/observability.service.ts +328 -0
- package/src/core/observability/observability.store.ts +269 -0
- package/src/core/observability/observability.types.ts +193 -0
- package/src/core/observability/observability.why.ts +206 -0
- package/src/core/observe/observe.service.ts +76 -0
- package/src/core/plan/plan.detect.ts +47 -0
- package/src/core/plan/plan.service.ts +81 -0
- package/src/core/plan/plan.types.ts +25 -0
- package/src/core/policy/policy.defaults.ts +103 -0
- package/src/core/policy/policy.guard.ts +34 -0
- package/src/core/policy/policy.integrity.ts +238 -0
- package/src/core/policy/policy.loader.ts +118 -0
- package/src/core/policy/policy.operator.ts +82 -0
- package/src/core/policy/policy.posture.ts +79 -0
- package/src/core/policy/policy.rails.ts +45 -0
- package/src/core/policy/policy.types.ts +157 -0
- package/src/core/presence/presence.service.ts +111 -0
- package/src/core/presence/presence.store.ts +59 -0
- package/src/core/presence/presence.types.ts +9 -0
- package/src/core/release/release.decisions.ts +147 -0
- package/src/core/release/release.seen.ts +38 -0
- package/src/core/shell-policy/shell-policy.service.ts +215 -0
- package/src/core/shell-policy/shell-policy.stall.ts +57 -0
- package/src/core/shell-policy/shell-policy.types.ts +26 -0
- package/src/core/ship/ship.ledger.ts +76 -0
- package/src/core/ship/ship.service.ts +137 -0
- package/src/core/ship/ship.types.ts +19 -0
- package/src/core/stagnation/stagnation.resolution.ts +87 -0
- package/src/core/stagnation/stagnation.service.ts +20 -0
- package/src/core/stagnation/stagnation.store.ts +49 -0
- package/src/core/stagnation/stagnation.types.ts +12 -0
- package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
- package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
- package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
- package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
- package/src/core/supply-chain/supply-chain.service.ts +217 -0
- package/src/core/turn/turn.activity.ts +94 -0
- package/src/core/turn/turn.autopilot.ts +131 -0
- package/src/core/turn/turn.failure-signals.ts +193 -0
- package/src/core/turn/turn.loop-counter.ts +71 -0
- package/src/core/turn/turn.types.ts +14 -0
- package/src/core/untrusted/untrusted.detect.ts +53 -0
- package/src/core/untrusted/untrusted.recall.ts +87 -0
- package/src/core/untrusted/untrusted.service.ts +129 -0
- package/src/core/untrusted/untrusted.store.ts +75 -0
- package/src/core/untrusted/untrusted.types.ts +42 -0
- package/src/entrypoints/compact-before.ts +14 -0
- package/src/entrypoints/prompt-submit.ts +25 -0
- package/src/entrypoints/response-after.ts +60 -0
- package/src/entrypoints/run.ts +179 -0
- package/src/entrypoints/session-end.ts +58 -0
- package/src/entrypoints/session-start.ts +172 -0
- package/src/entrypoints/shim.ts +46 -0
- package/src/entrypoints/stop.ts +876 -0
- package/src/entrypoints/subagent-start.ts +16 -0
- package/src/entrypoints/subagent-stop.ts +32 -0
- package/src/entrypoints/support.ts +182 -0
- package/src/entrypoints/tool-after.ts +111 -0
- package/src/entrypoints/tool-before.ts +186 -0
- package/src/entrypoints/tool-failure.ts +26 -0
- package/src/platform/backoff.ts +49 -0
- package/src/platform/cli-output.ts +30 -0
- package/src/platform/env-scope.ts +22 -0
- package/src/platform/fs-atomic.ts +142 -0
- package/src/platform/fs-jsonl.ts +24 -0
- package/src/platform/git.ts +187 -0
- package/src/platform/paths.ts +80 -0
- package/src/platform/pricing.ts +229 -0
- package/src/platform/process.ts +64 -0
- package/src/platform/sanitize.ts +23 -0
- package/src/platform/screen.ts +46 -0
- package/src/platform/style.ts +103 -0
- package/src/providers/claude/claude.capabilities.ts +25 -0
- package/src/providers/claude/claude.detect.ts +14 -0
- package/src/providers/claude/claude.inbound.ts +252 -0
- package/src/providers/claude/claude.lessons-view.ts +33 -0
- package/src/providers/claude/claude.outbound.ts +65 -0
- package/src/providers/claude/claude.policy-defaults.ts +9 -0
- package/src/providers/claude/claude.transcript.ts +62 -0
- package/src/providers/claude/claude.wiring.ts +245 -0
- package/src/providers/claude/index.ts +17 -0
- package/src/providers/cursor/cursor.capabilities.ts +28 -0
- package/src/providers/cursor/cursor.detect.ts +14 -0
- package/src/providers/cursor/cursor.inbound.ts +203 -0
- package/src/providers/cursor/cursor.lessons-view.ts +30 -0
- package/src/providers/cursor/cursor.outbound.ts +43 -0
- package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
- package/src/providers/cursor/cursor.wiring.ts +214 -0
- package/src/providers/cursor/index.ts +17 -0
- package/src/providers/index.ts +9 -0
- package/src/providers/provider.degrade.ts +152 -0
- package/src/providers/provider.port.ts +20 -0
- package/src/providers/provider.registry.ts +28 -0
- package/tools/doctor.ts +631 -0
- package/tools/help-topic.ts +74 -0
- package/tools/init-project.ts +277 -0
- package/tools/install-runtime.ts +164 -0
- package/tools/lessons-cli.ts +474 -0
- package/tools/obs-cli.ts +175 -0
- package/tools/price-lookup.ts +57 -0
- package/tools/refresh-model-prices.ts +210 -0
- package/tools/test-env.mjs +28 -0
- package/tools/test-env.names.d.mts +1 -0
- package/tools/test-env.names.mjs +14 -0
- package/tools/uninstall-runtime.ts +463 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Concept
|
|
3
|
+
title: "Harness architecture"
|
|
4
|
+
description: "Ports-and-adapters shape of the harness: contracts, core, providers, entrypoints, and how the tlc CLI and runtime home fit together."
|
|
5
|
+
tags: [architecture, core, providers, contracts]
|
|
6
|
+
timestamp: "2026-07-29"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Harness architecture
|
|
10
|
+
|
|
11
|
+
## Two layers
|
|
12
|
+
|
|
13
|
+
| Layer | Where | Role |
|
|
14
|
+
|-------|--------|------|
|
|
15
|
+
| **Global runtime** | `~/.tlc/harness/` + provider hook wiring | Hooks, policy engine, observability, CLI |
|
|
16
|
+
| **Project policy** | `<repo>/.tlc/harness/config.json` + provider shim hooks | Per-repo lint/test/ship/subagent choices |
|
|
17
|
+
|
|
18
|
+
Global does the work. Project only configures *this* repo. Do not copy `~/.tlc/harness/src` into projects.
|
|
19
|
+
|
|
20
|
+
```mermaid
|
|
21
|
+
graph LR
|
|
22
|
+
subgraph one["One machine"]
|
|
23
|
+
R["~/.tlc/harness<br/><i>runtime, CLI, hooks</i>"]
|
|
24
|
+
CU["cursor config<br/><i>hooks.json</i>"]
|
|
25
|
+
CL["claude config<br/><i>settings.json</i>"]
|
|
26
|
+
end
|
|
27
|
+
subgraph repo["One repository, shared by both"]
|
|
28
|
+
PC["config.json<br/><i>policy, tracked</i>"]
|
|
29
|
+
ST["state/<br/><i>handoff, lessons, obs</i>"]
|
|
30
|
+
end
|
|
31
|
+
CU --> R
|
|
32
|
+
CL --> R
|
|
33
|
+
R --> PC
|
|
34
|
+
R --> ST
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Both providers read the same policy and write the same state, so two people on one repository in different
|
|
38
|
+
tools see one handoff.
|
|
39
|
+
|
|
40
|
+
See [/decisions/ad-002.md](/decisions/ad-002.md) for why the CLI is `tlc harness …` and the runtime home is
|
|
41
|
+
`~/.tlc/harness/`, not `~/.cursor/agent-harness/`.
|
|
42
|
+
|
|
43
|
+
## Module layout (ports and adapters)
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
src/
|
|
47
|
+
├── contracts/ shared vocabulary: HarnessEvent, Decision, ProviderCapabilities, EffortLevel, ProviderWiring
|
|
48
|
+
├── core/ provider-agnostic steering logic, organized flat-by-aggregate
|
|
49
|
+
├── providers/ one anti-corruption-layer adapter per provider (cursor/, claude/)
|
|
50
|
+
├── platform/ OS-portable primitives (paths, atomic fs, git, process, pricing)
|
|
51
|
+
└── entrypoints/ one file per hook handler, composing core + the resolved provider
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Arrows are imports, and every one of them is checked mechanically:
|
|
55
|
+
|
|
56
|
+
```mermaid
|
|
57
|
+
graph TD
|
|
58
|
+
E["entrypoints/<br/><i>one file per hook handler</i>"]
|
|
59
|
+
C["core/<br/><i>steering logic, provider-agnostic</i>"]
|
|
60
|
+
P["providers/<br/><i>one ACL adapter per provider</i>"]
|
|
61
|
+
K["contracts/<br/><i>shared vocabulary</i>"]
|
|
62
|
+
L["platform/<br/><i>OS primitives</i>"]
|
|
63
|
+
|
|
64
|
+
E --> C
|
|
65
|
+
E --> P
|
|
66
|
+
C --> K
|
|
67
|
+
P --> K
|
|
68
|
+
C --> L
|
|
69
|
+
P --> L
|
|
70
|
+
C -.->|forbidden| P
|
|
71
|
+
P -.->|forbidden| C
|
|
72
|
+
|
|
73
|
+
style K fill:#eef,stroke:#557
|
|
74
|
+
style C fill:#efe,stroke:#575
|
|
75
|
+
style P fill:#fee,stroke:#755
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`core/` never imports `providers/`; `providers/` never imports `core/`; `contracts/` imports neither and is
|
|
79
|
+
imported by both. `tools/dev/check-boundaries.ts` enforces this mechanically, plus a scan that forbids vendor
|
|
80
|
+
identifiers (`cursor`, `claude`, `codex`, `composer`, `anthropic`) anywhere under `src/core/` or
|
|
81
|
+
`src/contracts/`. See [/decisions/ad-004.md](/decisions/ad-004.md) and
|
|
82
|
+
[/decisions/ad-010.md](/decisions/ad-010.md).
|
|
83
|
+
|
|
84
|
+
Two rules keep the gate from going quiet about itself. It runs `biome check --error-on-warnings`, because a
|
|
85
|
+
warn-level diagnostic does not change biome's exit code and three fixable warnings had therefore survived several
|
|
86
|
+
green runs — one of them a parameter accepted and never used. And `tools/check-suppressions.ts` fails it on any
|
|
87
|
+
`biome-ignore`, `@ts-ignore` or `@ts-expect-error` whose reason does not open with `why:`, `hazard:` or `invariant:`
|
|
88
|
+
followed by a real sentence, because a stricter exit code creates pressure to silence rather than fix. See
|
|
89
|
+
[/decisions/ad-051.md](/decisions/ad-051.md).
|
|
90
|
+
|
|
91
|
+
Each provider adapter implements the same port (`ProviderPort` in `src/providers/provider.port.ts`):
|
|
92
|
+
`detect`, `capabilities`, `policyDefaults`, `toEvent`, `render`, `wiring`. Core receives a `HarnessEvent`
|
|
93
|
+
and a `ProviderCapabilities` descriptor as plain data — it never branches on a provider's name. See
|
|
94
|
+
[/providers/index.md](/providers/index.md) for the full port shape and the two registered adapters.
|
|
95
|
+
|
|
96
|
+
## One event, end to end
|
|
97
|
+
|
|
98
|
+
```mermaid
|
|
99
|
+
sequenceDiagram
|
|
100
|
+
participant Ed as Editor
|
|
101
|
+
participant Hk as Hook file
|
|
102
|
+
participant Lx as tlc-exec
|
|
103
|
+
participant Ad as Provider adapter
|
|
104
|
+
participant Fl as Floor
|
|
105
|
+
participant Co as Core
|
|
106
|
+
participant Dg as degrade
|
|
107
|
+
|
|
108
|
+
Ed->>Hk: tool call, shell, read or stop
|
|
109
|
+
Hk->>Lx: provider-shaped JSON on stdin
|
|
110
|
+
Lx->>Ad: resolve provider by payload shape
|
|
111
|
+
Ad->>Fl: HarnessEvent
|
|
112
|
+
Fl-->>Ad: deny, reading no config
|
|
113
|
+
Fl->>Co: allow, carry on
|
|
114
|
+
Co->>Dg: Decision + ProviderCapabilities
|
|
115
|
+
Dg->>Ad: Decision the provider can express
|
|
116
|
+
Ad->>Hk: provider-shaped JSON on stdout
|
|
117
|
+
Hk->>Ed: allow, deny, ask or injected context
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The adapter is the only layer that speaks the provider's dialect. The floor decides before any policy is
|
|
121
|
+
loaded, and `degrade` guarantees the answer fits what the provider can actually do.
|
|
122
|
+
|
|
123
|
+
## Degradation, not detection
|
|
124
|
+
|
|
125
|
+
When a core `Decision` (`allow | deny | ask | context | continue | rewriteInput | abstain`) cannot be
|
|
126
|
+
expressed on a given provider, `src/providers/provider.degrade.ts` degrades it based on the capability
|
|
127
|
+
descriptor:
|
|
128
|
+
|
|
129
|
+
- Provider cannot enforce hooks at all → any enforcing decision becomes an `ADVISORY —` context message.
|
|
130
|
+
- `ask` where `askSupportedOn` does not include the current event → becomes `deny` (a provider that cannot
|
|
131
|
+
ask must not silently allow).
|
|
132
|
+
- `rewriteInput` where `toolInputRewrite` is false → becomes `ask`, carrying the proposed input in the
|
|
133
|
+
reason text.
|
|
134
|
+
- `context` truncates to a caller-supplied character budget, dropping `env` if `sessionEnv` is false.
|
|
135
|
+
|
|
136
|
+
This is what lets a hookless or partially-capable provider be a new adapter file rather than a core
|
|
137
|
+
refactor.
|
|
138
|
+
|
|
139
|
+
## Runtime (Bun-first, Node-guaranteed)
|
|
140
|
+
|
|
141
|
+
| Piece | Rule |
|
|
142
|
+
|-------|------|
|
|
143
|
+
| **Preferred** | Bun on `PATH` — every hook runs the TypeScript source directly, no compile step, ~1 ms per invocation |
|
|
144
|
+
| **Fallback** | Node.js **24+** running `dist/*.mjs` — ~27 ms per invocation |
|
|
145
|
+
| **Launcher** | `bin/tlc-exec.mjs` (Node, all platforms); `bin/tlc-exec` (Unix), `bin/tlc-exec.cmd` (Windows) |
|
|
146
|
+
| **Build** | `tlc harness build` / `bin/tlc-build` (needs Bun or esbuild once to compile `dist/`) |
|
|
147
|
+
| **Forbidden** | Requiring Bun to *use* the harness; flooring on EOL Node |
|
|
148
|
+
|
|
149
|
+
The measured numbers and the full trade-off are recorded in [/decisions/ad-012.md](/decisions/ad-012.md).
|
|
150
|
+
Provider hooks only need a command on PATH plus JSON stdin/stdout — neither provider requires Bun.
|
|
151
|
+
|
|
152
|
+
## Load order
|
|
153
|
+
|
|
154
|
+
`DEFAULTS` → `~/.tlc/harness/config.json` → `<repo>/.tlc/harness/config.json`
|
|
155
|
+
|
|
156
|
+
## Shim
|
|
157
|
+
|
|
158
|
+
Project hooks call `tlc-exec shim <handler>`. If `TLC_ACTIVE=1` (set by the user-level `sessionStart`
|
|
159
|
+
hook), the shim no-ops so hooks do not double-fire. Cloud agents without a user-level install run the real
|
|
160
|
+
handler via the shim path.
|
|
161
|
+
|
|
162
|
+
## Operator postures
|
|
163
|
+
|
|
164
|
+
Three postures, one dimension: how much the agent surfaces and what earns an interruption. Verification is
|
|
165
|
+
identical at all three — the same evidence bar, the same gates, the same done-criteria
|
|
166
|
+
([/decisions/ad-025.md](/decisions/ad-025.md)).
|
|
167
|
+
|
|
168
|
+
| Posture | What reaches the operator |
|
|
169
|
+
|---------|---------------------------|
|
|
170
|
+
| `paired` | Any sizable move, asked before it happens — plus the three below |
|
|
171
|
+
| `solo` | An irreversible or destructive action, a real dead-end, ambiguity that changes the outcome |
|
|
172
|
+
| `focus` | A destructive action or a real dead-end only; ambiguity is settled under a stated assumption |
|
|
173
|
+
|
|
174
|
+
Each posture also states a **deadline**: an unclear goal belongs in the first actions, and once work is under way
|
|
175
|
+
the agent states an assumption rather than asking, because a late question is worse than a decision. `focus`
|
|
176
|
+
admits exactly one early question ([/decisions/ad-026.md](/decisions/ad-026.md)).
|
|
177
|
+
|
|
178
|
+
`paired` is enforced, not only stated: a shell command is asked about before it runs when it reaches the network,
|
|
179
|
+
when it can overwrite or remove an existing path, or when it changes who can reach a path — the last because that
|
|
180
|
+
change appears in no diff. An append is not asked about: a prompt the operator learns to clear without reading is
|
|
181
|
+
the mechanism by which a consequential action gets waved through. `solo` and
|
|
182
|
+
`focus` differ in what the agent raises, which is its judgement rather than a hook event, so their enforcement is
|
|
183
|
+
the same.
|
|
184
|
+
|
|
185
|
+
Every one of those decisions is recorded with its posture and its rule, so the interruption rate is attributable
|
|
186
|
+
and the threshold can be calibrated from real sessions.
|
|
187
|
+
|
|
188
|
+
Set it with `tlc harness mode <paired|solo|focus>`, or `mode` in the project config. Any other value is
|
|
189
|
+
refused — `status` and `doctor` name the rejected word and the posture running in its place.
|
|
190
|
+
|
|
191
|
+
## Day-to-day vs grind
|
|
192
|
+
|
|
193
|
+
Grind is **off by default** and independent of posture. Enable with `tlc harness grind`.
|
|
194
|
+
|
|
195
|
+
## Steering pillars (product core)
|
|
196
|
+
|
|
197
|
+
Observability and cost are support. The product is **stop → followup → handoff → policy**.
|
|
198
|
+
|
|
199
|
+
### Floor — no configuration reaches it
|
|
200
|
+
|
|
201
|
+
Evaluated before any policy is loaded, so no setting and no agent edit can clear it
|
|
202
|
+
([/decisions/ad-016.md](/decisions/ad-016.md)).
|
|
203
|
+
|
|
204
|
+
Each denial names its rule, so `rule=secret-access` in a message maps to a row here. The table is generated
|
|
205
|
+
from `src/core/floor/floor.catalog.ts`, which is keyed by the `FloorRule` union — a rule added to the union
|
|
206
|
+
and not described there fails the typecheck.
|
|
207
|
+
|
|
208
|
+
<!-- generated:floor -->
|
|
209
|
+
|
|
210
|
+
| Rule | Denies | Allowed anyway |
|
|
211
|
+
|---|---|---|
|
|
212
|
+
| `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 |
|
|
213
|
+
| `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 |
|
|
214
|
+
| `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 |
|
|
215
|
+
| `history-rewrite` | `git push --force` | `--force-with-lease`, which refuses on its own when the remote moved |
|
|
216
|
+
| `machine-control` | `shutdown`, `reboot`, `halt`, `poweroff` | — |
|
|
217
|
+
| `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 |
|
|
218
|
+
| `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 |
|
|
219
|
+
|
|
220
|
+
<!-- /generated -->
|
|
221
|
+
|
|
222
|
+
Three more checks are equally unconfigurable and are **not** floor rules — they run after it, and each
|
|
223
|
+
detects a condition that a config field could otherwise switch off:
|
|
224
|
+
|
|
225
|
+
| Check | Rule | Effect |
|
|
226
|
+
|-------|------|--------|
|
|
227
|
+
| Policy integrity | `policy-baseline-divergence` | A policy source changed mid-session with no `tlc harness` command behind it. Acting calls are refused until `tlc harness policy accept` clears it; reads pass, so the agent can investigate |
|
|
228
|
+
| Policy surface, tool half | `policy-surface-write` | Agent writes to the same paths through Write, Edit, Delete, MultiEdit or NotebookEdit ([/decisions/ad-022.md](/decisions/ad-022.md)) |
|
|
229
|
+
| Edit collision | `edit-collision` | Two agents editing the same file in one working tree are told, not silently merged |
|
|
230
|
+
|
|
231
|
+
### Tunable rails
|
|
232
|
+
|
|
233
|
+
<!-- generated:rails -->
|
|
234
|
+
|
|
235
|
+
| Rail | Effect | Status |
|
|
236
|
+
|------|--------|--------|
|
|
237
|
+
| Grind (lint/test on stop) | Re-checks lint/test after each completed turn and follow-ups until gates pass. | `grind.enabled` |
|
|
238
|
+
| Ship gate | Blocks false done after an explicit HARNESS_SHIP_CLAIM when evidence is missing. | `shipGate.enabled` |
|
|
239
|
+
| Empty-diff anti-ship | Blocks a ship claim when the working tree has zero changes. | `shipGate.emptyDiffAntiShip` |
|
|
240
|
+
| Comment gate (agent-added comments) | Blocks the stop when this turn added comment lines, so narration never lands. Diff-scoped: comments you already committed are never flagged. | `comments.enabled` |
|
|
241
|
+
| Duplication gate (agent-added copies) | Blocks the stop when the turn wrote six or more lines the project already has, naming both sites. Diff-scoped: what was already duplicated is not counted. | `duplication.enabled` |
|
|
242
|
+
| Supply-chain gate (dependencies this turn added) | Blocks the stop when a dependency added this turn is not recorded in a lockfile, or is specified as latest/*/no version. A dependency added in a turn runs on every later turn, in CI, and on every machine that installs the project. | `supplyChain.enabled` |
|
|
243
|
+
| Subagent allowlist | Restricts Task/subagent models to a list you write, and blocks *-fast shapes by default. | `subagents.enforceAllowlist` |
|
|
244
|
+
| Block parent Fast mode for Task spawns | Denies Task/subagentStart while the parent chat is in Fast mode (sticky from hooks), closing the gap where Task slugs omit *-fast. | `subagents.blockParentFast` |
|
|
245
|
+
| Shell stall detection | Blocks repeating the exact same shell command too many times. | `shell.stallDetection` |
|
|
246
|
+
| Catastrophic shell ask | Asks before destructive shell commands (rm -rf, drop db, force push, …). | `shell.catastrophicAsk` |
|
|
247
|
+
| Lessons | Records compact lessons on gate stagnation and reinjects them ranked under a char budget. A lesson can name the path or symbol that makes it true and is withheld once that stops resolving, can carry an end date, and is graded helped or neutral by the next run of the gate it was injected for. Three tiers: shipped core, a global tier read by every product on this machine, and this project's own. How the lessons reach the model is decided by the provider rather than configured: where a host does not deliver context returned from its session-start hook, a durable rules file is written instead. | `intelligence.lessons.enabled` |
|
|
248
|
+
| Budget continue | Pushes the agent to keep working under context pressure instead of wrapping up early. | `intelligence.budgetContinue` |
|
|
249
|
+
| Gap feedback | Injects PREVIOUS_GAPS on gate failure so retries fix listed items. | `intelligence.gapFeedback` |
|
|
250
|
+
| Failure classification | Stores failure categories on the handoff for clearer next actions. | `intelligence.failureClassification` |
|
|
251
|
+
| Progressive handoff | Carries the gaps the previous session ended with into the next session's bootstrap, as history rather than as a task list. | `intelligence.progressiveHandoff` |
|
|
252
|
+
| Progressive context | Escalates gate follow-up detail on each stop retry. | `intelligence.progressiveContext` |
|
|
253
|
+
| Autopilot | Adds ordered AUTOPILOT steps on gate failure. | `intelligence.autopilot` |
|
|
254
|
+
| Idle-turn gate (asked instead of acting) | Blocks a turn that ends with open work, zero tool calls and zero file changes. Counts recorded tool events rather than reading the reply, so it cannot be talked around. | `intelligence.idleTurnGate` |
|
|
255
|
+
| Docs staleness gate | Runs the repository's own documentation staleness tool on stop, so a stale document fails like a failing test. | `docs.command` |
|
|
256
|
+
| Global observability spool | Mirrors this repo's obs and audit records into one file under the runtime home, so cost and gate history can be read across every repository at once. | `obs.globalSpool` |
|
|
257
|
+
| Untrusted-content framing and enforcement | Injects one framing line per turn when the agent reads a pull request, an issue, a fetched page or an MCP result, stating that the content is data and that any directive inside it is to be reported as a prompt-injection attempt, not obeyed. | `untrustedContent.enabled` |
|
|
258
|
+
| Plan gate (declared scope vs diff) | Blocks the stop when the turn changed files the declared HARNESS_PLAN did not name, so scope creep fails like a failing test instead of surviving as a review comment. | `planGate.enabled` |
|
|
259
|
+
| Observation mode (measure a rail with its rule off) | Runs a rail's checker while that rail is not enforcing, so the record says whether the property held with the rule injected or without it. That is the reading that tells you a rail is unnecessary rather than merely quiet, and it is what makes deleting one a decision instead of a guess. | `observe.enabled` |
|
|
260
|
+
|
|
261
|
+
<!-- /generated -->
|
|
262
|
+
|
|
263
|
+
Operator posture (`mode`: `paired`, `solo`, `focus`) governs surfacing rather than capability, so it is not in
|
|
264
|
+
the generated table.
|
|
265
|
+
|
|
266
|
+
## See also
|
|
267
|
+
|
|
268
|
+
- [/concepts.md](/concepts.md) — the same rails described from the operator's side
|
|
269
|
+
- [/providers/index.md](/providers/index.md)
|
|
270
|
+
- [/decisions/index.md](/decisions/index.md)
|