@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.
Files changed (300) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/LICENSE +84 -0
  3. package/NOTICE +9 -0
  4. package/README.md +480 -0
  5. package/bin/tlc +10 -0
  6. package/bin/tlc-build +80 -0
  7. package/bin/tlc-cli.ts +1509 -0
  8. package/bin/tlc-exec +10 -0
  9. package/bin/tlc-exec.cmd +4 -0
  10. package/bin/tlc-exec.d.mts +60 -0
  11. package/bin/tlc-exec.mjs +248 -0
  12. package/bin/tlc.cmd +4 -0
  13. package/bin/tlc.mjs +7 -0
  14. package/bin/write-user-hooks.d.mts +40 -0
  15. package/bin/write-user-hooks.mjs +123 -0
  16. package/capabilities/catalog.json +418 -0
  17. package/config.example.json +51 -0
  18. package/dist/compact-before.mjs +7839 -0
  19. package/dist/doctor.mjs +8263 -0
  20. package/dist/help-topic.mjs +82 -0
  21. package/dist/init-project.mjs +993 -0
  22. package/dist/install-runtime.mjs +969 -0
  23. package/dist/lessons-cli.mjs +7258 -0
  24. package/dist/obs-cli.mjs +7025 -0
  25. package/dist/price-lookup.mjs +221 -0
  26. package/dist/prompt-submit.mjs +7860 -0
  27. package/dist/refresh-model-prices.mjs +158 -0
  28. package/dist/response-after.mjs +7879 -0
  29. package/dist/run.mjs +7830 -0
  30. package/dist/session-end.mjs +7937 -0
  31. package/dist/session-start.mjs +8056 -0
  32. package/dist/shim.mjs +52 -0
  33. package/dist/stop.mjs +8494 -0
  34. package/dist/subagent-start.mjs +7861 -0
  35. package/dist/subagent-stop.mjs +7851 -0
  36. package/dist/support.mjs +7045 -0
  37. package/dist/tlc-cli.mjs +8063 -0
  38. package/dist/tool-after.mjs +8113 -0
  39. package/dist/tool-before.mjs +7985 -0
  40. package/dist/tool-failure.mjs +7850 -0
  41. package/dist/uninstall-runtime.mjs +1307 -0
  42. package/docs/architecture.md +270 -0
  43. package/docs/concepts.md +562 -0
  44. package/docs/coverage.md +55 -0
  45. package/docs/decisions/ad-001.md +32 -0
  46. package/docs/decisions/ad-002.md +51 -0
  47. package/docs/decisions/ad-003.md +30 -0
  48. package/docs/decisions/ad-004.md +37 -0
  49. package/docs/decisions/ad-005.md +36 -0
  50. package/docs/decisions/ad-006.md +49 -0
  51. package/docs/decisions/ad-007.md +36 -0
  52. package/docs/decisions/ad-008.md +54 -0
  53. package/docs/decisions/ad-009.md +61 -0
  54. package/docs/decisions/ad-010.md +45 -0
  55. package/docs/decisions/ad-011.md +59 -0
  56. package/docs/decisions/ad-012.md +71 -0
  57. package/docs/decisions/ad-013.md +87 -0
  58. package/docs/decisions/ad-014.md +56 -0
  59. package/docs/decisions/ad-015.md +33 -0
  60. package/docs/decisions/ad-016.md +98 -0
  61. package/docs/decisions/ad-017.md +65 -0
  62. package/docs/decisions/ad-018.md +77 -0
  63. package/docs/decisions/ad-019.md +75 -0
  64. package/docs/decisions/ad-020.md +88 -0
  65. package/docs/decisions/ad-021.md +57 -0
  66. package/docs/decisions/ad-022.md +120 -0
  67. package/docs/decisions/ad-023.md +87 -0
  68. package/docs/decisions/ad-024.md +85 -0
  69. package/docs/decisions/ad-025.md +92 -0
  70. package/docs/decisions/ad-026.md +131 -0
  71. package/docs/decisions/ad-027.md +90 -0
  72. package/docs/decisions/ad-028.md +90 -0
  73. package/docs/decisions/ad-029.md +73 -0
  74. package/docs/decisions/ad-030.md +97 -0
  75. package/docs/decisions/ad-031.md +94 -0
  76. package/docs/decisions/ad-032.md +83 -0
  77. package/docs/decisions/ad-033.md +89 -0
  78. package/docs/decisions/ad-034.md +86 -0
  79. package/docs/decisions/ad-035.md +86 -0
  80. package/docs/decisions/ad-036.md +68 -0
  81. package/docs/decisions/ad-037.md +47 -0
  82. package/docs/decisions/ad-038.md +52 -0
  83. package/docs/decisions/ad-039.md +69 -0
  84. package/docs/decisions/ad-040.md +89 -0
  85. package/docs/decisions/ad-041.md +98 -0
  86. package/docs/decisions/ad-042.md +82 -0
  87. package/docs/decisions/ad-043.md +79 -0
  88. package/docs/decisions/ad-044.md +61 -0
  89. package/docs/decisions/ad-045.md +94 -0
  90. package/docs/decisions/ad-046.md +111 -0
  91. package/docs/decisions/ad-047.md +96 -0
  92. package/docs/decisions/ad-048.md +85 -0
  93. package/docs/decisions/ad-049.md +66 -0
  94. package/docs/decisions/ad-050.md +94 -0
  95. package/docs/decisions/ad-051.md +69 -0
  96. package/docs/decisions/ad-052.md +69 -0
  97. package/docs/decisions/ad-053.md +78 -0
  98. package/docs/decisions/ad-054.md +98 -0
  99. package/docs/decisions/ad-055.md +74 -0
  100. package/docs/decisions/ad-056.md +85 -0
  101. package/docs/decisions/ad-057.md +68 -0
  102. package/docs/decisions/ad-058.md +97 -0
  103. package/docs/decisions/ad-059.md +82 -0
  104. package/docs/decisions/ad-060.md +75 -0
  105. package/docs/decisions/ad-061.md +68 -0
  106. package/docs/decisions/ad-062.md +72 -0
  107. package/docs/decisions/ad-063.md +84 -0
  108. package/docs/decisions/ad-064.md +79 -0
  109. package/docs/decisions/ad-065.md +81 -0
  110. package/docs/decisions/ad-066.md +111 -0
  111. package/docs/decisions/ad-067.md +64 -0
  112. package/docs/decisions/ad-068.md +79 -0
  113. package/docs/decisions/ad-069.md +74 -0
  114. package/docs/decisions/ad-070.md +86 -0
  115. package/docs/decisions/ad-071.md +93 -0
  116. package/docs/decisions/ad-072.md +82 -0
  117. package/docs/decisions/ad-073.md +102 -0
  118. package/docs/decisions/ad-074.md +91 -0
  119. package/docs/decisions/ad-075.md +79 -0
  120. package/docs/decisions/ad-076.md +102 -0
  121. package/docs/decisions/ad-077.md +94 -0
  122. package/docs/decisions/ad-078.md +84 -0
  123. package/docs/decisions/ad-079.md +73 -0
  124. package/docs/decisions/ad-080.md +86 -0
  125. package/docs/decisions/ad-081.md +70 -0
  126. package/docs/decisions/ad-082.md +79 -0
  127. package/docs/decisions/ad-083.md +88 -0
  128. package/docs/decisions/index.md +111 -0
  129. package/docs/diagnose.md +248 -0
  130. package/docs/index.md +52 -0
  131. package/docs/init.md +101 -0
  132. package/docs/lessons.md +294 -0
  133. package/docs/log.md +139 -0
  134. package/docs/measure.md +178 -0
  135. package/docs/providers/claude-code.md +118 -0
  136. package/docs/providers/cursor.md +110 -0
  137. package/docs/providers/index.md +71 -0
  138. package/docs/troubleshooting.md +138 -0
  139. package/model-aliases.json +12 -0
  140. package/model-prices.cursor.json +410 -0
  141. package/model-prices.json +1 -0
  142. package/package.json +57 -0
  143. package/skills/harness-init/SKILL.md +241 -0
  144. package/skills/harness-init/evals/trigger_evals.json +86 -0
  145. package/skills/harness-init/references/capabilities.md +113 -0
  146. package/src/contracts/capabilities.ts +30 -0
  147. package/src/contracts/decision.ts +21 -0
  148. package/src/contracts/effort.ts +15 -0
  149. package/src/contracts/harness-event.ts +87 -0
  150. package/src/contracts/index.ts +6 -0
  151. package/src/contracts/policy-defaults.ts +14 -0
  152. package/src/contracts/wiring.ts +20 -0
  153. package/src/core/attest/attest.service.ts +0 -0
  154. package/src/core/capability/capability.service.ts +62 -0
  155. package/src/core/capability/capability.store.ts +51 -0
  156. package/src/core/capability/capability.types.ts +56 -0
  157. package/src/core/comment-policy/comment-policy.service.ts +307 -0
  158. package/src/core/comment-policy/comment-policy.types.ts +6 -0
  159. package/src/core/comment-policy/comment-resolvability.ts +134 -0
  160. package/src/core/comment-policy/comment-syntax.catalog.ts +310 -0
  161. package/src/core/comment-policy/comment-syntax.store.ts +65 -0
  162. package/src/core/comment-policy/comment-syntax.types.ts +24 -0
  163. package/src/core/core.facade.ts +461 -0
  164. package/src/core/duplication/duplication.service.ts +219 -0
  165. package/src/core/floor/floor.catalog.ts +54 -0
  166. package/src/core/floor/floor.paths.ts +90 -0
  167. package/src/core/floor/floor.policy-surface.ts +311 -0
  168. package/src/core/floor/floor.service.ts +306 -0
  169. package/src/core/floor/floor.tokenize.ts +179 -0
  170. package/src/core/floor/floor.verb.ts +28 -0
  171. package/src/core/floor/index.ts +1 -0
  172. package/src/core/gate/gate.artifact.ts +157 -0
  173. package/src/core/gate/gate.command.ts +130 -0
  174. package/src/core/gate/gate.findings.ts +201 -0
  175. package/src/core/gate/gate.inputs.ts +0 -0
  176. package/src/core/gate/gate.lock.ts +283 -0
  177. package/src/core/gate/gate.service.ts +28 -0
  178. package/src/core/gate/gate.types.ts +67 -0
  179. package/src/core/handoff/handoff.service.ts +43 -0
  180. package/src/core/handoff/handoff.store.ts +59 -0
  181. package/src/core/handoff/handoff.types.ts +89 -0
  182. package/src/core/index.ts +52 -0
  183. package/src/core/integrity/state-seal.ts +81 -0
  184. package/src/core/lesson/lesson.authored.ts +96 -0
  185. package/src/core/lesson/lesson.credit.ts +51 -0
  186. package/src/core/lesson/lesson.garden.ts +283 -0
  187. package/src/core/lesson/lesson.link.ts +93 -0
  188. package/src/core/lesson/lesson.score.ts +61 -0
  189. package/src/core/lesson/lesson.select.ts +209 -0
  190. package/src/core/lesson/lesson.service.ts +110 -0
  191. package/src/core/lesson/lesson.store.ts +317 -0
  192. package/src/core/lesson/lesson.sync.ts +70 -0
  193. package/src/core/lesson/lesson.types.ts +92 -0
  194. package/src/core/lesson/lesson.validity.ts +44 -0
  195. package/src/core/observability/observability.report.ts +326 -0
  196. package/src/core/observability/observability.service.ts +328 -0
  197. package/src/core/observability/observability.store.ts +269 -0
  198. package/src/core/observability/observability.types.ts +193 -0
  199. package/src/core/observability/observability.why.ts +206 -0
  200. package/src/core/observe/observe.service.ts +76 -0
  201. package/src/core/plan/plan.detect.ts +47 -0
  202. package/src/core/plan/plan.service.ts +81 -0
  203. package/src/core/plan/plan.types.ts +25 -0
  204. package/src/core/policy/policy.defaults.ts +103 -0
  205. package/src/core/policy/policy.guard.ts +34 -0
  206. package/src/core/policy/policy.integrity.ts +238 -0
  207. package/src/core/policy/policy.loader.ts +118 -0
  208. package/src/core/policy/policy.operator.ts +82 -0
  209. package/src/core/policy/policy.posture.ts +79 -0
  210. package/src/core/policy/policy.rails.ts +45 -0
  211. package/src/core/policy/policy.types.ts +157 -0
  212. package/src/core/presence/presence.service.ts +111 -0
  213. package/src/core/presence/presence.store.ts +59 -0
  214. package/src/core/presence/presence.types.ts +9 -0
  215. package/src/core/release/release.decisions.ts +147 -0
  216. package/src/core/release/release.seen.ts +38 -0
  217. package/src/core/shell-policy/shell-policy.service.ts +215 -0
  218. package/src/core/shell-policy/shell-policy.stall.ts +57 -0
  219. package/src/core/shell-policy/shell-policy.types.ts +26 -0
  220. package/src/core/ship/ship.ledger.ts +76 -0
  221. package/src/core/ship/ship.service.ts +137 -0
  222. package/src/core/ship/ship.types.ts +19 -0
  223. package/src/core/stagnation/stagnation.resolution.ts +87 -0
  224. package/src/core/stagnation/stagnation.service.ts +20 -0
  225. package/src/core/stagnation/stagnation.store.ts +49 -0
  226. package/src/core/stagnation/stagnation.types.ts +12 -0
  227. package/src/core/subagent-policy/subagent-policy.parent-model.ts +202 -0
  228. package/src/core/subagent-policy/subagent-policy.service.ts +139 -0
  229. package/src/core/subagent-policy/subagent-policy.types.ts +22 -0
  230. package/src/core/supply-chain/supply-chain.catalog.ts +51 -0
  231. package/src/core/supply-chain/supply-chain.service.ts +217 -0
  232. package/src/core/turn/turn.activity.ts +94 -0
  233. package/src/core/turn/turn.autopilot.ts +131 -0
  234. package/src/core/turn/turn.failure-signals.ts +193 -0
  235. package/src/core/turn/turn.loop-counter.ts +71 -0
  236. package/src/core/turn/turn.types.ts +14 -0
  237. package/src/core/untrusted/untrusted.detect.ts +53 -0
  238. package/src/core/untrusted/untrusted.recall.ts +87 -0
  239. package/src/core/untrusted/untrusted.service.ts +129 -0
  240. package/src/core/untrusted/untrusted.store.ts +75 -0
  241. package/src/core/untrusted/untrusted.types.ts +42 -0
  242. package/src/entrypoints/compact-before.ts +14 -0
  243. package/src/entrypoints/prompt-submit.ts +25 -0
  244. package/src/entrypoints/response-after.ts +60 -0
  245. package/src/entrypoints/run.ts +179 -0
  246. package/src/entrypoints/session-end.ts +58 -0
  247. package/src/entrypoints/session-start.ts +172 -0
  248. package/src/entrypoints/shim.ts +46 -0
  249. package/src/entrypoints/stop.ts +876 -0
  250. package/src/entrypoints/subagent-start.ts +16 -0
  251. package/src/entrypoints/subagent-stop.ts +32 -0
  252. package/src/entrypoints/support.ts +182 -0
  253. package/src/entrypoints/tool-after.ts +111 -0
  254. package/src/entrypoints/tool-before.ts +186 -0
  255. package/src/entrypoints/tool-failure.ts +26 -0
  256. package/src/platform/backoff.ts +49 -0
  257. package/src/platform/cli-output.ts +30 -0
  258. package/src/platform/env-scope.ts +22 -0
  259. package/src/platform/fs-atomic.ts +142 -0
  260. package/src/platform/fs-jsonl.ts +24 -0
  261. package/src/platform/git.ts +187 -0
  262. package/src/platform/paths.ts +80 -0
  263. package/src/platform/pricing.ts +229 -0
  264. package/src/platform/process.ts +64 -0
  265. package/src/platform/sanitize.ts +23 -0
  266. package/src/platform/screen.ts +46 -0
  267. package/src/platform/style.ts +103 -0
  268. package/src/providers/claude/claude.capabilities.ts +25 -0
  269. package/src/providers/claude/claude.detect.ts +14 -0
  270. package/src/providers/claude/claude.inbound.ts +252 -0
  271. package/src/providers/claude/claude.lessons-view.ts +33 -0
  272. package/src/providers/claude/claude.outbound.ts +65 -0
  273. package/src/providers/claude/claude.policy-defaults.ts +9 -0
  274. package/src/providers/claude/claude.transcript.ts +62 -0
  275. package/src/providers/claude/claude.wiring.ts +245 -0
  276. package/src/providers/claude/index.ts +17 -0
  277. package/src/providers/cursor/cursor.capabilities.ts +28 -0
  278. package/src/providers/cursor/cursor.detect.ts +14 -0
  279. package/src/providers/cursor/cursor.inbound.ts +203 -0
  280. package/src/providers/cursor/cursor.lessons-view.ts +30 -0
  281. package/src/providers/cursor/cursor.outbound.ts +43 -0
  282. package/src/providers/cursor/cursor.policy-defaults.ts +9 -0
  283. package/src/providers/cursor/cursor.wiring.ts +214 -0
  284. package/src/providers/cursor/index.ts +17 -0
  285. package/src/providers/index.ts +9 -0
  286. package/src/providers/provider.degrade.ts +152 -0
  287. package/src/providers/provider.port.ts +20 -0
  288. package/src/providers/provider.registry.ts +28 -0
  289. package/tools/doctor.ts +631 -0
  290. package/tools/help-topic.ts +74 -0
  291. package/tools/init-project.ts +277 -0
  292. package/tools/install-runtime.ts +164 -0
  293. package/tools/lessons-cli.ts +474 -0
  294. package/tools/obs-cli.ts +175 -0
  295. package/tools/price-lookup.ts +57 -0
  296. package/tools/refresh-model-prices.ts +210 -0
  297. package/tools/test-env.mjs +28 -0
  298. package/tools/test-env.names.d.mts +1 -0
  299. package/tools/test-env.names.mjs +14 -0
  300. 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)