loki-mode 8.5.2 → 8.6.0

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.
@@ -39,12 +39,23 @@
39
39
  # 2 BLOCKED
40
40
  # 3 verifier error (could not complete; never silently passes)
41
41
  #
42
- # NOTE on exit-code divergence from the spec: spec Section 1.1 lists
43
- # 0=VERIFIED, 1=BLOCKED, 2=CONCERNS, 3=error (BLOCKED and CONCERNS swapped
44
- # vs this implementation). This module follows the explicit BUILD TASK
45
- # ordering (0/1/2 = VERIFIED/CONCERNS/BLOCKED). A human must reconcile the
46
- # two before the GitHub App (Phase 2) consumes exit codes. The divergence is
47
- # surfaced in `loki verify --help`.
42
+ # EXIT-CODE ORDERING, RECONCILED. An early draft spec listed
43
+ # 0=VERIFIED, 1=BLOCKED, 2=CONCERNS, and this header used to say a human must
44
+ # reconcile the two before the GitHub App consumed exit codes. That is now
45
+ # done, in favor of THIS implementation: 0/1/2/3 =
46
+ # VERIFIED/CONCERNS/BLOCKED/error.
47
+ #
48
+ # Why this ordering wins. The code is authoritative and always has been
49
+ # (VERIFY_EXIT_* below), `loki verify --help` documents it, and
50
+ # wiki/CLI-Reference.md documents it. The draft spec that said otherwise is not
51
+ # in this repository and has no consumers. Renumbering working code to match an
52
+ # absent document would break every existing caller to satisfy nothing.
53
+ #
54
+ # It is also the ordering an integrator expects: severity rises with the code,
55
+ # so `[ $rc -ge 2 ]` means "at least blocked". The reverse would make 1 more
56
+ # severe than 2, which no one guesses correctly.
57
+ #
58
+ # See docs/exit-codes.md for the exit codes of every command.
48
59
 
49
60
  set -uo pipefail
50
61
 
@@ -1810,6 +1821,7 @@ verify_emit_evidence() {
1810
1821
  _VERIFY_GATES="$_VERIFY_GATES_FILE" \
1811
1822
  _V_VERDICT="$VERIFY_VERDICT" \
1812
1823
  _V_EXIT="$VERIFY_EXIT" \
1824
+ _V_JSON_STDOUT="${VERIFY_JSON:-0}" \
1813
1825
  _V_SCHEMA="$VERIFY_SCHEMA_VERSION" \
1814
1826
  _V_TOOLVER="$tool_version" \
1815
1827
  _V_REPO="$repo_name" \
@@ -1833,7 +1845,7 @@ verify_emit_evidence() {
1833
1845
  _V_SCOPE_MAX_FILES="${VERIFY_SCOPE_MAX_FILES:-}" \
1834
1846
  _V_SCOPE_MAX_NET="${VERIFY_SCOPE_MAX_NET_LINES:-}" \
1835
1847
  python3 - <<'PYEOF'
1836
- import json, os, hashlib
1848
+ import json, os, hashlib, sys
1837
1849
 
1838
1850
  out_dir = os.environ["_VERIFY_OUT_DIR"]
1839
1851
  findings_file = os.environ["_VERIFY_FINDINGS"]
@@ -1959,6 +1971,27 @@ with open(ev_path, "w") as f:
1959
1971
  json.dump(doc, f, indent=2)
1960
1972
  f.write("\n")
1961
1973
 
1974
+ # --json emits the SAME document to stdout. Deliberately the same `doc` rather
1975
+ # than a second serializer: two writers of one contract drift, and the drift
1976
+ # shows up as a caller trusting a field the file no longer has. The evidence
1977
+ # file is still written either way, so --json adds a pipe without removing the
1978
+ # artifact.
1979
+ #
1980
+ # Written to FD 3, not stdout. The caller redirects this function's stdout to
1981
+ # /dev/null (it emits progress chatter the human path does not want), so a
1982
+ # plain stdout write here is discarded. FD 3 is opened by the caller only under
1983
+ # --json and routed to the real stdout.
1984
+ if os.environ.get("_V_JSON_STDOUT") == "1":
1985
+ try:
1986
+ with os.fdopen(os.dup(3), "w") as _jf:
1987
+ _jf.write(json.dumps(doc, indent=2) + "\n")
1988
+ except OSError:
1989
+ # FD 3 not open: --json was requested but the caller did not wire it.
1990
+ # Fail loudly rather than exit 0 having emitted nothing, which would
1991
+ # look to a pipeline like a verify that produced an empty document.
1992
+ sys.stderr.write("verify: --json requested but FD 3 is not open\n")
1993
+ raise SystemExit(3)
1994
+
1962
1995
  # ----- Markdown report -----
1963
1996
  def sev_rank(s):
1964
1997
  return {"Critical": 0, "High": 1, "Medium": 2, "Low": 3, "Info": 4}.get(s, 5)
@@ -2050,6 +2083,11 @@ OPTIONS:
2050
2083
  Default: critical,high (one notch looser than the
2051
2084
  Loki build loop, which also blocks on medium).
2052
2085
  --no-llm Accepted for forward-compat; LLM is already off in MVP.
2086
+ --json Emit the evidence document to stdout so it can be piped
2087
+ (`loki verify --json | jq .verdict`). The same document
2088
+ is still written to <out>/evidence.json. The human
2089
+ VERDICT banner moves to stderr so stdout stays valid
2090
+ JSON. The verdict and exit code are unchanged.
2053
2091
  --explain Print a one-screen, skeptic-legible trust proof: every
2054
2092
  gate that ran, its status, the runner/scanner that
2055
2093
  produced the evidence, whether it is reproducible, plus
@@ -2093,16 +2131,16 @@ VERDICT MODEL:
2093
2131
  Uncommitted working-tree changes are not verified; commit them first. An
2094
2132
  empty diff yields CONCERNS (nothing to verify), never VERIFIED.
2095
2133
 
2096
- EXIT CODES (this implementation):
2134
+ EXIT CODES:
2097
2135
  0 VERIFIED
2098
2136
  1 CONCERNS
2099
2137
  2 BLOCKED
2100
2138
  3 verifier error (could not complete; never silently passes)
2101
2139
 
2102
- NOTE: the verification spec (Section 1.1) lists 1=BLOCKED, 2=CONCERNS.
2103
- This implementation follows the build-task ordering (1=CONCERNS,
2104
- 2=BLOCKED). A human must reconcile the two before the GitHub App consumes
2105
- these codes.
2140
+ Severity rises with the code, so `[ $rc -ge 2 ]` means "at least blocked".
2141
+ An early draft spec listed 1=BLOCKED, 2=CONCERNS; that ordering was
2142
+ rejected and is not used anywhere. See docs/exit-codes.md for every
2143
+ command's codes.
2106
2144
 
2107
2145
  OUTPUT:
2108
2146
  <out>/evidence.json consolidated machine-readable evidence (schema 1.0)
@@ -2620,6 +2658,8 @@ verify_main() {
2620
2658
  # verify does NOT re-run gates: it reads a prior evidence.json and fails
2621
2659
  # closed if the repo has drifted since that evidence was graded.
2622
2660
  VERIFY_CHECK_FRESH=0
2661
+ # Opt-in machine-readable stdout (--json). Default 0 = exactly today.
2662
+ VERIFY_JSON=0
2623
2663
 
2624
2664
  # Fail-closed defaults. These globals are read at the end of this function
2625
2665
  # (the VERDICT banner and the function return code). verify_compute_verdict()
@@ -2644,6 +2684,16 @@ verify_main() {
2644
2684
  block_on="$(printf '%s' "${2:-}" | tr '[:upper:]' '[:lower:]')"; shift 2 ;;
2645
2685
  --no-llm)
2646
2686
  shift ;;
2687
+ --json)
2688
+ # Emit the evidence document to STDOUT so a caller can pipe it.
2689
+ # The same document is still written to <out>/evidence.json --
2690
+ # this adds a pipe, it does not move the artifact.
2691
+ #
2692
+ # Implies --quiet-banner: stdout must be JSON and nothing else,
2693
+ # or `loki verify --json | jq` breaks on the human banner. The
2694
+ # banner still goes to stderr, so an operator watching a
2695
+ # terminal loses nothing.
2696
+ VERIFY_JSON=1; shift ;;
2647
2697
  --explain)
2648
2698
  # Render a one-screen, skeptic-legible trust proof: every gate
2649
2699
  # that ran, its status, the runner/scanner that produced the
@@ -2793,10 +2843,21 @@ verify_main() {
2793
2843
 
2794
2844
  completed_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
2795
2845
 
2796
- verify_emit_evidence "$out_dir" "$started_at" "$completed_at" "$block_on" >/dev/null || {
2797
- _verify_err "failed to emit evidence document"
2798
- return $VERIFY_EXIT_ERROR
2799
- }
2846
+ # stdout is discarded (the emitter's own chatter is not wanted on the human
2847
+ # path). Under --json, FD 3 is opened onto the REAL stdout so the emitter
2848
+ # can write the evidence document there while everything else stays
2849
+ # suppressed -- that is what keeps `loki verify --json | jq` parseable.
2850
+ if [ "${VERIFY_JSON:-0}" = "1" ]; then
2851
+ verify_emit_evidence "$out_dir" "$started_at" "$completed_at" "$block_on" 3>&1 >/dev/null || {
2852
+ _verify_err "failed to emit evidence document"
2853
+ return $VERIFY_EXIT_ERROR
2854
+ }
2855
+ else
2856
+ verify_emit_evidence "$out_dir" "$started_at" "$completed_at" "$block_on" >/dev/null || {
2857
+ _verify_err "failed to emit evidence document"
2858
+ return $VERIFY_EXIT_ERROR
2859
+ }
2860
+ fi
2800
2861
 
2801
2862
  # Opt-in (--hosted): fold the embedded Autonomi Verify engine's verdict
2802
2863
  # fields into the just-written evidence.json. Fully additive and fail-open:
@@ -2814,9 +2875,14 @@ verify_main() {
2814
2875
  _verify_render_explain "$started_at" "$completed_at" "$VERIFY_VERDICT"
2815
2876
  fi
2816
2877
 
2817
- printf 'VERDICT: %s\n' "$VERIFY_VERDICT"
2818
- printf 'Evidence: %s/evidence.json\n' "$out_dir"
2819
- printf 'Report: %s/report.md\n' "$out_dir"
2878
+ # Under --json stdout carries the evidence document alone, so the human
2879
+ # banner is redirected to stderr rather than dropped: an operator watching a
2880
+ # terminal still sees the verdict, and `| jq` still parses.
2881
+ _v_banner_fd=1
2882
+ [ "${VERIFY_JSON:-0}" = "1" ] && _v_banner_fd=2
2883
+ printf 'VERDICT: %s\n' "$VERIFY_VERDICT" >&$_v_banner_fd
2884
+ printf 'Evidence: %s/evidence.json\n' "$out_dir" >&$_v_banner_fd
2885
+ printf 'Report: %s/report.md\n' "$out_dir" >&$_v_banner_fd
2820
2886
  # --hosted only: surface the enrichment in human output so the extra signal
2821
2887
  # is visible without parsing JSON. Printed solely when a fold succeeded;
2822
2888
  # the default path never sets VERIFY_HOSTED_SUMMARY, so it stays byte-identical.
package/completions/_loki CHANGED
@@ -153,6 +153,7 @@ function _loki_commands {
153
153
  'memory:Memory commands'
154
154
  'compound:Knowledge compounding commands'
155
155
  'checkpoint:Snapshot and restore session state'
156
+ 'cp:Snapshot and restore session state (alias of checkpoint)'
156
157
  'council:Completion council commands'
157
158
  'dogfood:Self-development statistics'
158
159
  'projects:Project registry commands'
@@ -5,7 +5,7 @@ _loki_completion() {
5
5
  _init_completion || return
6
6
 
7
7
  # Main subcommands (must match autonomy/loki main case statement)
8
- local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own doctor watchdog audit metrics syslog onboard share proof explain plan report cost kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
8
+ local main_commands="start quick monitor demo tour welcome init stop pause resume steer status next ship dashboard web serve api sandbox notify import github issue config provider reset memory compound checkpoint council dogfood projects enterprise secrets cockpit secure own handoff doctor watchdog audit metrics syslog onboard share proof receipt explain plan report cost kpis stats test ci watch telemetry agent context ctx code run export review optimize heal modernize migrate cluster worktree wt trigger failover remote deploy docker mcp magic assets analyze compliance crash open otel preview quickstart rc rollback self-update sentrux setup-skill spec state template trust trust-metrics ultracode update verify voice why wiki bench cleanup logs grill docs cp version completions help"
9
9
 
10
10
  # 1. If we are on the first argument (subcommand)
11
11
  if [[ $cword -eq 1 ]]; then
@@ -7,7 +7,7 @@ Modules:
7
7
  control: Session control API (start/stop/pause/resume)
8
8
  """
9
9
 
10
- __version__ = "8.5.2"
10
+ __version__ = "8.6.0"
11
11
 
12
12
  # Expose the control app for easy import
13
13
  try:
@@ -154,6 +154,34 @@ expose something the CLI does not. If a competitor does ship output
154
154
  verification, this test is how we would find out, and the claim above would be
155
155
  corrected rather than defended.
156
156
 
157
+ ## 7. Headless latency, five trials per tool
158
+
159
+ Same task, same machine, artifact verified by content, five trials each:
160
+
161
+ | CLI | Success | Median | Range |
162
+ | --- | --- | --- | --- |
163
+ | opencode 1.18.9 | 5/5 | 11s | 4s to 265s |
164
+ | codex-cli 0.146.0 | 4/5 | 69s | 48s to 80s (one 300s timeout) |
165
+
166
+ **Read the range, not just the median.** opencode's first trial took 265
167
+ seconds and the next four took 4 to 12. A new user experiences the 265, not the
168
+ 11. Reporting only the median would hide the thing they will actually feel.
169
+
170
+ **The timeout is kept in.** Dropping codex's one failure would turn 4-of-5 into
171
+ an implied 5-of-5 and overstate reliability.
172
+
173
+ **Five trials, not one, and that is the point.** An earlier single-shot run of
174
+ this same codex command timed out, and publishing it would have recorded
175
+ "codex: timeout" as a fact about a competitor when the identical command
176
+ completed in 51 seconds minutes later. The spread is the finding.
177
+
178
+ **What this does not measure.** One trivial file-creation task is not a proxy
179
+ for build quality, multi-iteration work, or brownfield capability. It measures
180
+ headless invocation latency and nothing else. aider, Claude Code and
181
+ cursor-agent have not been run on this task; Devin and Replit Agent ship no
182
+ local CLI. Cost is not compared: codex ran on a free tier and opencode's
183
+ per-call cost was not recorded.
184
+
157
185
  ---
158
186
 
159
187
  ## What we do not have
package/docs/PRIVACY.md CHANGED
@@ -114,10 +114,33 @@ it sits below the base telemetry gate, every telemetry opt-out (`loki telemetry
114
114
  off` / `LOKI_TELEMETRY=off` / `DO_NOT_TRACK=1`) also disables it -- opt-out
115
115
  always wins.
116
116
 
117
+ ### 5. First-run blocker (anonymous, STRICT opt-in, default OFF)
118
+
119
+ A `first_run_blocked` event naming which CLASS of dependency stopped a first
120
+ run, sent at most ONCE per install. It sits behind the same strict second
121
+ opt-in as build-outcome analytics above (`LOKI_ANALYTICS=on`), so it is off by
122
+ default even with telemetry enabled.
123
+
124
+ It exists because we could see that a first run was ATTEMPTED and nothing about
125
+ whether it succeeded, which made "why does a trial not convert" unanswerable.
126
+ A real example we found and fixed: on a machine with no AI provider CLI, one
127
+ route ended with a dead end instead of pointing at `loki tour` (which needs no
128
+ provider, no key and no spend). Nobody could see that happening.
129
+
130
+ The ONLY field is `blocker`, clamped to this fixed enum:
131
+
132
+ no_provider | node | python3 | jq | git | curl | disk | skill_symlink | other
133
+
134
+ Anything not on that list becomes `other`. It is deliberately coarse: `node` is
135
+ enough to act on, and a version string or an install path would be a leak. It
136
+ NEVER sends paths, versions, hostnames, spec text, or command lines -- a test
137
+ (`tests/test-first-run-blocked-signal.sh`) feeds a filesystem path through the
138
+ real emitter and fails the build if anything but `other` reaches the payload.
139
+
117
140
  This document and the first-run notice describe ALL paths. The model is unified:
118
141
  opt-out always wins and disables everything; crash reporting and usage telemetry
119
- opt in together (default ON for individuals); build-outcome analytics needs its
120
- own explicit second opt-in on top (default OFF).
142
+ opt in together (default ON for individuals); build-outcome analytics and the
143
+ first-run blocker each need an explicit second opt-in on top (default OFF).
121
144
 
122
145
  ## What is collected (the whitelist)
123
146
 
@@ -0,0 +1,91 @@
1
+ # Running without egress
2
+
3
+ If your code cannot leave your network, most of this category is unavailable to
4
+ you regardless of what the sales conversation suggests.
5
+
6
+ ## Where the tools actually stand
7
+
8
+ Verified from vendor documentation, 2026-07-31:
9
+
10
+ | Tool | Air-gapped |
11
+ |---|---|
12
+ | Devin | **No.** Single-tenant VPC via AWS PrivateLink, customer-managed KMS, a federal docs tree -- but "Devin's brain... always resides within Cognition's Cloud." |
13
+ | Cursor | No. Cloud service; embeddings are uploaded (obfuscated and encrypted). |
14
+ | Claude Code | Partly. Runs against Bedrock / Vertex / Foundry in your own cloud, so data residency is yours -- but a model endpoint is still required. |
15
+ | Lovable, Replit, Emergent | No. Browser products on their infrastructure. |
16
+ | opencode | Structurally yes (MIT, self-hostable) -- but no SOC2, SSO, audit logs, or support. |
17
+
18
+ Devin's is the strongest enterprise packaging in the category and it still
19
+ cannot run disconnected. That is a structural property of a hosted control
20
+ plane, not an oversight.
21
+
22
+ ## What we measured
23
+
24
+ Executed 2026-07-31 with outbound HTTP forced through an unroutable proxy --
25
+ not a flag, not an assumption. Every one of these returned a real result with
26
+ egress severed:
27
+
28
+ | Command | Result |
29
+ |---|---|
30
+ | `loki version` | works |
31
+ | `loki doctor --json` | works |
32
+ | `loki plan <spec> --json` | works -- full cost and complexity estimate |
33
+ | `loki proof list` | works |
34
+ | `loki proof verify <id>` | works, and correctly reported `tree_drift: true` |
35
+ | `loki heal <repo> --assess --json` | works -- maturity, ranked targets, runtime |
36
+
37
+ The whole evaluate-before-you-buy path runs disconnected. You can assess a
38
+ legacy codebase, estimate what a build would cost, and verify an existing
39
+ receipt without a single packet leaving the machine.
40
+
41
+ `loki proof verify` deserves emphasis: an auditor can re-check a receipt against
42
+ the repository offline and get a genuine verdict, including detecting drift.
43
+ That is the property competitors' dashboard-bound verification cannot have.
44
+
45
+ ## The one required egress, stated plainly
46
+
47
+ ```sh
48
+ loki doctor --airgap
49
+ ```
50
+
51
+ prints the egress inventory. Today it reports exactly one REQUIRED point:
52
+
53
+ ```
54
+ REQUIRED model inference -> https://api.anthropic.com
55
+ Set ANTHROPIC_BASE_URL to an in-network gateway, or switch to a
56
+ local-weights provider.
57
+ optional telemetry [off] disable: loki telemetry off (default off)
58
+ ```
59
+
60
+ **We cannot run a build with no model at all.** Nobody can. What we can do is
61
+ let you point at a model you host: set `ANTHROPIC_BASE_URL` to an in-network
62
+ gateway, or run a provider with local weights. The engine abstracts over CLIs
63
+ rather than over one vendor's API.
64
+
65
+ Telemetry is off by default and every opt-out wins (`DO_NOT_TRACK=1`,
66
+ `LOKI_TELEMETRY=off`, `~/.loki/config`). The adoption instrumentation added in
67
+ v8.6.0 requires a second explicit opt-in on top of that -- see
68
+ [PRIVACY.md](./PRIVACY.md).
69
+
70
+ ## Why `unknown` is the right answer offline
71
+
72
+ `loki heal --assess` reports `dependency_staleness: unknown` and always will
73
+ without a network call. We know your manifest pins lodash 3.x; we do not know
74
+ what is current upstream, and we will not guess.
75
+
76
+ That refusal is what makes the assessment trustworthy inside a disconnected
77
+ network. A tool that fabricates a staleness number offline is more dangerous
78
+ than one that declines.
79
+
80
+ ## Honest limits
81
+
82
+ - **A model endpoint is required.** If you have no model at all -- not local,
83
+ not in-network -- we cannot build anything, and neither can anyone else.
84
+ - **The five mutating healing phases need a provider.** Only `--assess` is
85
+ genuinely zero-dependency.
86
+ - **Not measured here:** a full disconnected build against a local-weights
87
+ provider. The commands above were measured; that one was not, and this page
88
+ does not claim it.
89
+
90
+ See [Kubernetes air-gapped install](../deploy/helm/README.md) for the
91
+ cluster-side path.
@@ -0,0 +1,120 @@
1
+ # Working on a codebase you already have
2
+
3
+ Most of this category is built for starting from nothing. If you already have a
4
+ repository -- especially a large, private, awkward one -- your options narrow
5
+ fast.
6
+
7
+ ## Where the tools actually stand
8
+
9
+ Verified from vendor documentation, 2026-07-31:
10
+
11
+ | Tool | Existing repository |
12
+ |---|---|
13
+ | Lovable | **Cannot import one.** "You can only export from Lovable to GitHub"; two-way sync begins only after Lovable creates the repo. |
14
+ | Replit Agent | Imports GitHub (public and private), Figma, ZIP -- into Replit's environment. |
15
+ | Cursor | Indexes your repo; embeddings are uploaded, obfuscated and encrypted. |
16
+ | Devin | Indexes the repo, plus YAML blueprints producing snapshots each session boots from. |
17
+ | Loki | Runs in place, where the code already is. |
18
+
19
+ That last row is the whole difference, and it is not a preference. For a private
20
+ monorepo with internal dependencies and submodules, "upload it to our
21
+ environment" is often not a thing anyone is permitted to do.
22
+
23
+ **Credit where it is due:** Devin has the strongest documented modernization
24
+ story of the four -- named playbooks for COBOL, Java upgrades, and
25
+ SAS-to-PySpark. If you want a vendor-run modernization program, look at them
26
+ seriously. What follows is what we do differently, not a claim that they are
27
+ bad at this.
28
+
29
+ ## Start here: a read-only assessment that costs nothing
30
+
31
+ ```sh
32
+ loki heal ./your-repo --assess --json
33
+ ```
34
+
35
+ No provider call, no API key, no spend, no writes. It reports:
36
+
37
+ - a **maturity level** with the reason stated (for example: "No test/spec files
38
+ detected: changes are unguarded")
39
+ - **ranked targets** with blast-radius reasoning per file ("isolated (no inbound
40
+ imports -> low blast radius), 12 LOC")
41
+ - **debt signals**: test ratio, TODO density
42
+ - **the runtime it declares**: Node engine constraint, dependency count, and the
43
+ frameworks actually present in the manifest
44
+ - **dependency lock status**
45
+
46
+ This is the honest opening move: you learn where to start before committing to
47
+ anything.
48
+
49
+ ### What it deliberately does not tell you
50
+
51
+ `dependency_staleness` reports `unknown`, always, offline. We know your manifest
52
+ pins lodash 3.x; we do not know what is current upstream without a network call,
53
+ and we will not guess. That refusal is the same reason the assessment works
54
+ inside an air-gapped network at all.
55
+
56
+ ## The healing phases
57
+
58
+ ```sh
59
+ loki heal ./your-repo --phase archaeology # map dependencies, catalog friction
60
+ loki heal ./your-repo --phase stabilize # add observability and tests, no behavior change
61
+ loki heal ./your-repo --phase isolate # adapter boundaries between components
62
+ loki heal ./your-repo --phase modernize # replace one component at a time, behind adapters
63
+ loki heal ./your-repo --phase validate # prove behavioral equivalence against the baseline
64
+ ```
65
+
66
+ These call a provider and cost money. `--assess` does not.
67
+
68
+ ## Behavioral equivalence is the part worth arguing about
69
+
70
+ Every tool in this category will tell you it preserved your business logic.
71
+ Devin's COBOL page says it preserves "critical functionality." What none of them
72
+ document is a *procedure* for proving it.
73
+
74
+ That is the axis we build on:
75
+
76
+ - **characterization tests** capture what the system does today, quirks
77
+ included, before anything is modernized
78
+ - **friction classification** distinguishes accidental mess from load-bearing
79
+ weirdness -- the 30-second sleep that looks stupid and is actually a race-
80
+ condition fix nobody documented
81
+ - **a backward-compatibility auditor** blocks removal of unclassified friction
82
+ - **the validate phase** checks behavior against the recorded baseline
83
+
84
+ Then the [Evidence Receipt](../README.md#the-evidence-receipt-dont-trust-the-agent-check-it)
85
+ records what was proven and what was not, bound to the specific diff.
86
+
87
+ "We prove behavior is unchanged" is a stronger claim than "we preserve business
88
+ logic," and it is the one you can check.
89
+
90
+ The full procedure -- what each phase does, what the friction taxonomy
91
+ distinguishes, and where the safety gates sit -- is in
92
+ [skills/healing.md](../skills/healing.md), with the research it draws on in
93
+ [references/legacy-healing-patterns.md](../references/legacy-healing-patterns.md).
94
+
95
+ ### The friction question, concretely
96
+
97
+ The hardest part of a legacy migration is not translating syntax. It is telling
98
+ the difference between:
99
+
100
+ - a `sleep 30` that is genuinely dead weight, and
101
+ - a `sleep 30` that is the only thing preventing a race condition nobody wrote
102
+ down, whose author left in 2019
103
+
104
+ Delete the second and the system breaks in production, weeks later, in a way
105
+ nobody connects to the migration. This is why the auditor blocks removal of
106
+ *unclassified* friction: not because the friction is sacred, but because
107
+ "we do not know what this does yet" is a real state that deserves a name
108
+ instead of a guess.
109
+
110
+ ## Honest scope
111
+
112
+ - **Measured and working:** `--assess` on real repositories, verified by
113
+ execution.
114
+ - **Implemented, not measured here:** the five mutating phases need a provider
115
+ and real spend; this page does not claim an end-to-end benchmark we have not
116
+ published.
117
+ - **We do not do COBOL.** If your problem is a mainframe, we are not your
118
+ answer today.
119
+ - **Your code stays put.** Nothing is uploaded to us -- there is no "us" in the
120
+ data path. See [cost controls](./cost-controls.md) for how spend is bounded.
@@ -0,0 +1,88 @@
1
+ # Cost controls
2
+
3
+ You set the ceiling. We stop at it, and the receipt tells you where the money
4
+ went.
5
+
6
+ ## Why this page exists
7
+
8
+ "I paid for the AI's own mistakes" is one of the sharpest complaints in this
9
+ category, and it is worth being precise about how our model differs.
10
+
11
+ Most competitors sell credits. Lovable has already addressed the objection
12
+ directly -- their "Try to fix" button does not consume credits, and their
13
+ troubleshooting docs push you toward reverting or replanning instead of
14
+ retrying the same prompt. That is a good policy and we are not claiming to have
15
+ invented a better one.
16
+
17
+ **Our model is different in kind: you bring your own provider credentials.** We
18
+ never bill you, because we are never in the payment path. What we owe you
19
+ instead is a hard ceiling and an honest account of what was spent -- which is
20
+ what this page describes.
21
+
22
+ ## The three caps
23
+
24
+ They bound different things, and a run that stalls needs all three.
25
+
26
+ | Cap | Bounds | Default |
27
+ |---|---|---|
28
+ | `LOKI_BUDGET_LIMIT` | Total spend, in USD | unset (no cap) |
29
+ | `LOKI_MAX_ITERATIONS` | Number of iterations | 1000 |
30
+ | `LOKI_MAX_DURATION` | Wall-clock time | unset (no cap) |
31
+
32
+ ```sh
33
+ LOKI_BUDGET_LIMIT=25 loki start ./prd.md
34
+ loki start ./prd.md --max-duration 90m
35
+ loki config set budget 25
36
+ ```
37
+
38
+ **Why three and not one.** Spend and iterations both assume forward progress. A
39
+ run that *stalls* -- a hung provider call, a wedged subprocess -- burns hours
40
+ while spending almost nothing and completing no iteration, so neither of those
41
+ breakers ever trips. The wall-clock cap is the one that catches it. We added it
42
+ after a run burned $34 reaching an external timeout.
43
+
44
+ ## Hitting a cap is a FAILURE, not a success
45
+
46
+ This is the part that matters for anyone automating against us.
47
+
48
+ All three caps produce a **terminal failure**: exit 20 under
49
+ `LOKI_DURABLE_STATE=1`, with a distinct status (`budget_exceeded`,
50
+ `max_iterations_reached`, `max_duration_reached`) so the receipt and `loki why`
51
+ can tell you *which* ceiling you hit and therefore which one to raise.
52
+
53
+ `budget_exceeded` used to exit **0**, on the reasoning that a human would raise
54
+ the cap and resume. That is true at a terminal and false inside a Kubernetes
55
+ Job, where there is no human: the Job went Complete, the pipeline went green,
56
+ and an incomplete build looked finished. Exit 0 now means the work is finished
57
+ or a person deliberately stopped it -- never that we ran out of money mid-task.
58
+
59
+ See [exit codes](./exit-codes.md) for the full contract.
60
+
61
+ ## Preview the cost before spending anything
62
+
63
+ ```sh
64
+ loki plan ./prd.md --json
65
+ ```
66
+
67
+ Reports complexity, estimated iterations, token usage and cost **without
68
+ executing**. No provider call, no spend. `LOKI_CONFIG_DUMP=1 loki start ./prd.md`
69
+ prints the resolved configuration and exits, so you can confirm your caps are
70
+ actually set before a real run.
71
+
72
+ ## What you are charged for, stated plainly
73
+
74
+ Every iteration calls your provider, including iterations spent re-running
75
+ after a quality gate fails. **We do not exempt our own gate failures from your
76
+ budget**, and pretending otherwise would be dishonest -- those calls really do
77
+ consume your tokens.
78
+
79
+ What we do instead:
80
+
81
+ - the caps above bound the total, so a doom loop has a hard ceiling
82
+ - the Evidence Receipt records iteration count and cost, so a re-run is visible
83
+ rather than buried
84
+ - reaching a cap reports as a failure with the reason named, so you know whether
85
+ to raise the ceiling or narrow the spec
86
+
87
+ If a run cost more than you expected, `loki proof show <id>` tells you where it
88
+ went.