jevia 0.1.3

Outcome-aware model routing CLI
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
# Jevia

Jevia is an outcome-aware model router for coding agents. It asks Jev for a
typed routing decision, applies a deterministic safety policy, and records the
eventual result so later decisions can use evidence from earlier runs.

> Jevia is experimental. The current milestones establish the CLI, routing
> contract, local outcome store, Jev integration, and generic harness
> execution. The managed control plane remains separate.

## Why Jevia

Most model routers classify a task and immediately forget what happened.
Jevia closes that loop:

1. describe stable capability tiers rather than hard-coding model names;
2. ask Jev which tier should handle the current task;
3. fall back to a configured safe tier when confidence is low;
4. record the routing decision in the configured history store;
5. attach success or failure after the task finishes;
6. include recent outcomes as evidence in future routing decisions.

The application owns the policy. Jev supplies a structured decision signal.

## Quick start

Use the copyable `/install.sh` command on the
[Jevia landing page](https://jevia.vercel.app). It downloads a
checksum-verified binary for macOS or Linux on Intel or ARM; Rust and Cargo are
not required.

```bash
curl -fsSL https://jevia.vercel.app/install.sh | sh
jevia --version
jevia init
export TYPESAFE_API_KEY="your-key"
jevia check
jevia route "investigate an intermittent distributed-lock failure"
```

`jevia check` makes one live Jev request and requires a valid API key. Use
`jevia doctor` for configuration/storage checks without a Jev API request.
PostgreSQL storage checks do connect to the configured database.

To install the exact crates.io release with Rust 1.92 or newer:

```bash
cargo install jevia --version 0.1.3 --locked
```

To try unreleased development changes instead:

```bash
cargo install --git https://github.com/assistant-ui/jevia --locked jevia
```

Record the real result after the task completes:

```bash
jevia feedback <run-id> success
jevia runs
```

Machine-readable output is available for integrations:

```bash
jevia route --json "fix a typo in the README"
jevia runs --json
```

## Commands

| Command | Purpose |
| --- | --- |
| `jevia init` | Create `.jevia/config.toml` and local store rules. |
| `jevia harness setup <name>` | Preview an explicit harness template; back up and save only with `--apply`. |
| `jevia harness check <name> [--json]` | Inspect configuration and local executable candidates without launching programs or calling APIs. |
| `jevia route <task>` | Ask Jev for a tier and record the decision. |
| <code>jevia run &lt;harness&gt; &lt;task&gt;</code> | Route, launch a configured harness, and record its exit outcome. |
| `jevia runs` | Inspect recent records in the configured backend. |
| `jevia stats [--limit <records>] [--json]` | Summarize recent routing decisions, verified outcomes, manual feedback, and cache hits. |
| `jevia feedback <id> <outcome>` | Mark a run as `success`, `failure`, or `unknown`. |
| `jevia doctor` | Validate configuration, credentials, and configured storage. |
| `jevia storage setup <sqlite\|postgres>` | Preview database setup; explicitly apply after validation, backup, and optional JSONL import. |
| `jevia storage init` | Explicitly initialize an opt-in database schema and project. |
| `jevia storage check` | Check storage without needing a Jev API key. |
| `jevia storage check --deep` | Inspect all history records and SQL routing/order metadata without a write probe or repair. |
| `jevia storage import-jsonl [--from <file>] [--apply]` | Preview/import local history into a database without changing the source. |
| `jevia storage export --output <file>` | Export history to a new JSONL snapshot; never overwrite a file. |
| `jevia check` | Validate configured storage and complete a live Jev routing round trip without storing a run. |
| `jevia cache status` | Inspect routing-cache settings and entry counts. |
| `jevia cache clear` | Remove cached decisions without touching run history. |

Run `jevia <command> --help` for command-specific options.

## Routing insights

```sh
jevia stats
jevia stats --limit 500
jevia stats --json
```

`stats` reads the configured JSONL, SQLite, or PostgreSQL history without calling
Jev, launching a harness, changing outcomes, or reading the decision-cache file.
It needs no Jev API key; PostgreSQL still requires its configured database
connection. Initialize an opted-in database with `jevia storage init` first.
This command requires v0.1.2 or newer.

The default window is the **latest 1,000 records in append order**, not a date
range or an all-time total. `--limit` accepts 1–100,000. The report says when older
records were excluded; archived records are not included. SQL reads are bounded
and project-scoped. JSONL still scans and validates the full file under its shared
history lock, but retains only the requested tail in memory.

Totals and per-tier groups use the **selected tier recorded at routing time**,
including tiers since removed from configuration. Tiers are not concrete model
identities: changing a harness's model mapping does not split historical groups.
Each record counts once, using its current outcome and provenance:

- **Verified success rate:** verifier-backed successes divided by verifier-backed
  successes plus failures. Manual feedback, process-exit-only results, active
  runs, unknown outcomes, and legacy outcomes without provenance are excluded.
  A configured verifier's result is not a guarantee of task correctness.
- **Manual feedback:** separate success/failure counts. Correcting a verified
  outcome with manual feedback moves that record to the manual group; prior
  feedback events and the old verifier result are not counted again.
- **Learning evidence:** known, non-active verifier-backed or manual outcomes,
  using the same eligibility rule as routing. This is the eligible count in the
  stats window, not necessarily the smaller evidence window sent to Jev.
- **Cache hits:** recorded cached decisions divided by all records in the window,
  not current cache occupancy or a count of API calls. Bypassed/disabled-cache
  decisions still count in the denominator. Legacy records without a decision
  source use the existing `live` default.
- **Other outcomes:** process-exit-only and unattributed known outcomes, plus
  active and unknown counts. Active takes precedence over unknown, so the outcome
  groups partition the records without double-counting.

Rates with no eligible observations display `n/a`, not 0%. The JSON report uses
`schema_version: 1`, `storage`, `window` (`limit`, `order`, `has_older_records`),
`totals`, and a tier-keyed `tiers` map. Rates are fractions from 0 to 1, or `null`
when their denominator is zero. `verified`, `manual`, `process_exit`, and
`unattributed` each contain `successes` and `failures`; `active` and `unknown`
are separate counts. Output excludes task text, run IDs, feedback notes,
execution commands, and connection URLs; historical tier labels are included.

These are descriptive, potentially small or biased samples—not a model ranking,
a controlled benchmark, or proof that adaptive routing improves results. No
cost savings are estimated because token/cost telemetry is not recorded.

## Harness adapters

### Preview-first setup

`jevia init` leaves harness selection to you. Configure your installed agent with
an explicit argument template and one model mapping for every configured tier:

```sh
jevia harness setup agent --command my-agent \
  --arg=run --arg=--model --arg='{model}' --arg='{task}' \
  --model fast=provider/small \
  --model balanced=provider/standard \
  --model strong=provider/frontier \
  --verify-command cargo --verify-arg=test
```

This is a generic example, not a provider preset: substitute your agent's actual
executable, argument syntax, and accessible model IDs. Repeat the same command
with `--apply` after reviewing its TOML preview. Setup never launches either
program, calls Jev, opens storage, or checks provider credentials/model access.
It works offline, including when a configured database is unavailable. There
is no interactive prompt or automatic agent installation. Quote placeholders as
shown and use `--arg=--flag` / `--verify-arg=--flag` for leading-hyphen arguments.

- Preview changes no files. Apply shares a configuration lock with database setup,
  saves a private exact-byte backup in ignored `.jevia/config-backups/`, then
  atomically replaces the config after checking for concurrent edits. It preserves
  unrelated settings, harnesses, and comments, and rejects symlink configs. Stop
  concurrent manual config editing; the lock only coordinates Jevia setup commands.
- Changing an existing harness also requires `--replace`. Reapplying identical
  settings does not rewrite the config or make another backup (apply may create
  the config lock sidecar). The selected harness entry is rewritten when changed.
- Omitted verifier options preserve an existing verifier. Use `--no-verification`
  to explicitly remove it; supplying `--verify-command` replaces its whole command
  and argument list. Without verification, process success alone is not eligible
  learning evidence. Missing/duplicate/unknown tier mappings and unsupported
  template placeholders are rejected before config replacement.
- Commands, arguments, and model IDs appear in previews and committed config.
  Never put credentials in these flags or templates; let the agent inherit its
  credentials from the environment. Protect retained config backups, especially
  with appropriate directory ACLs on Windows. Nothing is shell-expanded by Jevia.

### Local preflight

```sh
jevia harness check agent
jevia harness check agent --json
```

Preflight validates the project config and selected adapter, renders its templates
for every configured tier, rejects NUL arguments, and checks file candidates for
the agent and optional verifier. It requires no Jev/provider key or database
connection, does not read history/cache, and creates no files or locks. It never
executes even a `--version` probe. Missing executables, model mappings, or valid
templates produce a failing exit status. An absent verifier is a warning, not a
failure; process success alone still is not learning evidence.

The human report escapes the requested harness name. JSON reports use
`schema_version: 1`, `harness`, `scope: "static"`, `ok`, `checks` (stable `id`,
`status`, `code`, and explanatory `message`), and `limitations`. Once a project
is found, failed checks also produce JSON and exit nonzero. CLI argument errors
and missing projects retain normal CLI diagnostics. Reports omit configured
command paths, arguments, model IDs, credentials, and raw parse/driver errors.

Lookup checks explicit paths or the inherited `PATH`; it does not expand shell
aliases, variables, `~`, or `PATHEXT`. Unix relative paths/PATH entries are checked
against the project root, with regular-file and execute-bit checks. On Windows,
bare names may omit `.exe`; non-`.exe` extensions must be explicit. Preflight
requires absolute explicit paths and absolute PATH entries on Windows, and warns
that it does not search extra system/application directories. This intentionally
conservative check is not a complete reproduction of OS executable resolution;
use an absolute path if lookup is ambiguous. Windows batch wrappers get a warning
because [Rust launches them through `cmd.exe`](https://doc.rust-lang.org/std/process/index.html#windows-argument-splitting).

An `ok` result means **static checks passed**, not that an agent is authenticated
or will successfully launch. Binary format, interpreters, mount/ACL restrictions,
agent-specific flags, provider model access, and task correctness are not tested;
files and environment may change afterward. Preflight does not change `jevia run`
or the existing `jevia check` live routing probe.

### Manual configuration

Harness adapters are shell-free process templates. Add a harness to
<code>.jevia/config.toml</code> and map every capability tier to a concrete
model:

~~~toml
[harnesses.agent]
command = "my-agent"
args = ["run", "--model", "{model}", "{task}"]

[harnesses.agent.models]
fast = "provider/small"
balanced = "provider/standard"
strong = "provider/frontier"

[harnesses.agent.verification]
command = "cargo"
args = ["test", "--workspace", "--all-features"]
~~~

A complete ready-to-copy configuration is available at
[examples/jevia.toml](https://github.com/assistant-ui/jevia/blob/main/crates/jevia-cli/examples/jevia.toml).

Then route and run a task through that adapter:

~~~bash
jevia run agent "investigate the failing integration test"
~~~

Extra harness arguments must follow <code>--</code> and are appended without
shell interpretation:

~~~bash
jevia run agent "update the parser" -- --verbose
~~~

Templates support <code>{task}</code>, <code>{model}</code>,
<code>{tier}</code>, and <code>{run_id}</code>. Jevia requires the task and
model placeholders, rejects unknown placeholders, launches the configured
executable directly, and mirrors its exit code. A non-zero harness exit records
failure and skips verification. Without a configured verifier, a zero harness
exit records success for backward compatibility.

When <code>verification</code> is configured, Jevia runs it only after the
harness succeeds and uses its exit status as the final outcome. A verifier that
cannot start leaves the outcome unknown, preventing an environment problem
from incorrectly training the router. Verification arguments support the same
placeholders and are also launched directly without shell interpretation.

Completed harness runs also record the concrete model, harness name, duration,
process exit code, and verification evidence. This data appears in
<code>jevia runs --json</code> and is supplied with relevant outcomes on later
routing requests, so model changes do not erase which implementation actually
produced a verified result.

## Node.js API

Use the typed `jevia` npm package when a Node application or agent framework
owns harness execution. It calls the CLI's shell-free JSON interface, keeping
routing policy, storage, caching, and outcome handling in one implementation:

~~~bash
npm install jevia
~~~

~~~ts
import { JeviaClient } from "jevia";

const jevia = new JeviaClient({ cwd: process.cwd() });
const task = "investigate the failing integration test";
const route = await jevia.route(task);

const model = {
  fast: "provider/small",
  balanced: "provider/standard",
  strong: "provider/frontier",
}[route.tier];

const result = await runYourHarness({ task, model, runId: route.run_id });
const verified = await verifyResult(result);
await jevia.feedback(route.run_id, verified ? "success" : "failure");
~~~

The adapter can call Codex, Claude Code, OpenCode, Gemini CLI, Cursor Agent,
Copilot CLI, Aider, Goose, Amp, or a custom harness. Jevia returns a capability
tier; the application maps that tier to a harness-specific model. Feedback stays
explicit—a successful process or function return is not automatically proof of
task success. The CLI must already be installed and available on `PATH`; npm
installation does not run a binary downloader.

## Run lifecycle

Execution progress is separate from task outcome. New records track `routed`,
`running`, `verifying`, `completed`, `launch_failed`, or `interrupted`, with start
and finish timestamps. A completed process may still have a failed task outcome.

```sh
jevia runs show <run-id>
jevia runs recover <run-id>
```

`show` prints the complete record as JSON. `recover` explicitly marks a formerly
running/verifying execution as interrupted only if no Jevia supervisor holds its
per-run lease. It leaves the task outcome unknown and never reruns or terminates
processes. After a supervisor crash, inspect any surviving child processes and
workspace changes before starting new work. Legacy unknown outcomes are not
assumed to represent interrupted executions. Feedback on active runs is refused.

New and updated records use schema version 3; versions 1 and 2 remain readable.
Older CLI versions refuse version 3 rather than silently discard new metadata.
The ignored `run-leases/` sidecars are retained so concurrent processes always
coordinate on the same lock file.

### Bounded non-interactive execution

For a headless harness, opt into owned process-tree supervision:

```sh
jevia run agent "fix the parser" --non-interactive --timeout-seconds 300 --verification-timeout-seconds 120
```

Each deadline applies only to its execution phase, not the Jev routing request.
Both flags require `--non-interactive` and accept 1–86400 seconds. Unspecified
deadlines are unlimited. This mode disables stdin and uses a Unix process group
or Windows job object; stdout/stderr still stream normally. Ctrl-C (and SIGTERM
on Unix) stops the owned group/job and records `cancelled`; a deadline records
`timed_out`. Outcomes remain unknown, failed/cancelled harnesses skip verification,
and verification cancellation preserves the harness evidence. Exit codes are 130
for cancellation and 124 for timeout. Cleanup errors record `interrupted` instead
of claiming the process tree was stopped.

The main command exiting does not finish a supervised phase while background
processes remain in its group/job. Jevia waits for them before starting verification
or recording completion, and the phase deadline and cancellation remain active.
The main command's exit code is preserved; background commands must report their
own failures to the main command (or the verifier) if they should affect the outcome.

Without this flag, existing interactive terminal behavior remains unchanged.
This is not a sandbox: descendants that deliberately escape a process group/job,
SIGKILL of Jevia, and machine crashes cannot be handled reliably. Use explicit
recovery and inspect the workspace in those cases; no work is automatically retried.

## Outcome provenance

Run records distinguish `process_exit`, `verification`, and `manual` evidence.
Process-only success/failure remains visible, but only known outcomes from a
completed verifier or explicit human feedback are supplied to Jev as learning
evidence. Legacy outcomes without provenance are not silently promoted; confirm
them with `feedback` if you want them used in routing. `runs` reports the source
and whether the result is eligible for learning.

```sh
jevia feedback <run-id> success
jevia feedback <run-id> failure --reason "The integration test still fails"
jevia runs show <run-id>
```

Changing an already-known outcome requires a nonempty `--reason`. Every feedback
operation retains the prior outcome/source, timestamp, and optional reason in a
audit trail in the selected backend; original execution evidence is preserved. Reasons are limited
to 4096 bytes and are never sent to Jev. Setting the outcome to `unknown` removes
it from learning evidence. These records are not a tamper-proof audit log.

## Routing cache

Jevia caches equivalent routing decisions locally so repeated work does not
always require another network request. The default policy keeps up to 256
decisions for 15 minutes:

~~~toml
[cache]
enabled = true
ttl_seconds = 900
max_entries = 256
~~~

A cache key is a SHA-256 fingerprint over the exact task, Jev endpoint and
model, routing policy, tier definitions, selected harness mapping, and the
recent completed evidence actually sent to Jev. A new success, failure,
verification result, policy change, model change, or harness change therefore
produces a miss automatically. Pending outcomes do not invalidate an otherwise
equivalent decision.

Cache files contain the fingerprint and decision signal, not task text. Every
hit receives a fresh run ID and timestamp, and run records expose
<code>source=live</code> or <code>source=cache</code>. Use
<code>--no-cache</code> on <code>jevia route</code> or
<code>jevia run</code> when a forced live decision is needed.

Cache errors never block routing: Jevia reports the problem and falls through
to a live request. API errors are never cached, expired decisions are never
used as an offline fallback, and <code>jevia cache clear</code> provides an
explicit recovery path for a damaged cache.

Concurrent equivalent cache misses normally share one live routing request.
Waiters recheck both the cache and current learning evidence before using a
decision; they still receive independent run IDs. Coordination uses up to 256
stable lock stripes in the ignored `cache-leases/` directory, so lock files do
not grow per task. Unrelated requests can occasionally share a stripe and wait.
No global history/cache lock is held during network calls. An OS lease is
released if its owner exits; failures are not cached, so another caller can try.

Waiting is bounded to the configured Jev timeout plus one second (at most 30
seconds). On expiry or a coordination error, routing proceeds live; duplicate
requests are possible in that fallback. `--no-cache` skips coordination too.

## Storage

JSONL remains the default. SQLite and PostgreSQL are opt-in alternatives; changing
the backend does **not** synchronize or automatically move existing history.
The normal `route`, `run`, `runs`, `feedback`, `doctor`, and `check` commands use
the selected backend. These options require v0.1.2 or newer.

### SQLite: local database, no server

The guided CLI path avoids editing TOML by hand (run `jevia init` first):

```sh
jevia storage setup sqlite --import-jsonl
# Stop all Jevia writers/supervisors using this workspace, then:
jevia storage setup sqlite --import-jsonl --apply --confirm-stopped
jevia storage check
jevia stats
```

The first command previews without creating files or contacting a database.
`--path .jevia/custom.db` chooses another local file; paths are resolved from the
discovered project root even when invoked from a subdirectory. Protect and ignore
custom paths outside `.jevia` yourself. An empty JSONL project can omit
`--import-jsonl`; a nonempty one must include it to avoid silently abandoning
existing evidence. The source JSONL file is never deleted or rewritten.

Or configure manually:

Add to `.jevia/config.toml`:

```toml
[storage]
backend = "sqlite"
url = "sqlite://.jevia/jevia.db"
```

Then explicitly initialize and optionally import your old JSONL history:

```sh
jevia storage init
jevia storage check
jevia storage import-jsonl
jevia storage import-jsonl --apply
jevia runs
```

Relative file paths are resolved from the project root, not the current working
directory. SQLite is bundled into the binary. It uses WAL, full synchronous writes,
a five-second busy timeout, and private file permissions on Unix for new databases.
Use a local disk, not a shared/network filesystem. Default `.db` files, WAL/SHM
sidecars, and run locks under `.jevia` are ignored after `init`/`storage init`.
For custom paths/extensions, protect the directory and add your own ignore rules;
on Windows, protect the directory with the appropriate filesystem ACLs.

### PostgreSQL: bring your own database

Provision a dedicated PostgreSQL database and put its connection URL in your
secret manager or environment as `JEVIA_DATABASE_URL`. Do not put passwords in
the project config or CLI arguments.

```sh
jevia storage setup postgres --project my-project --import-jsonl
# Stop all Jevia writers/supervisors using this workspace, then:
jevia storage setup postgres --project my-project --import-jsonl --apply --confirm-stopped
jevia storage check
```

`--url-env MY_DATABASE_URL` selects a different environment variable **name**, not
a URL value. Preview does not resolve that variable or test connectivity. On apply,
the existing TLS and timeout rules apply; `--allow-insecure-localhost` is available
only for loopback development databases. The command creates the Jevia schema and
project, not a PostgreSQL server, database, or user.

The equivalent manual configuration is:

```toml
[storage]
backend = "postgres"
url_env = "JEVIA_DATABASE_URL"
project = "my-project"
```

Run `jevia storage init` once with schema-creation permissions, then
`jevia storage check`. Normal operation needs read/write access to the
`jevia_projects` and `jevia_runs` tables and read access to `jevia_schema`,
not permission to create databases. Remote connections require certificate and
hostname verification (`sslmode=verify-full`); `sslrootcert`, `sslcert`, `sslkey`,
and `application_name` URL options are supported. Only local development can opt
into plaintext using `allow_insecure_localhost = true` and a loopback host.

Use a **direct or session-pooled connection**, not a transaction-mode pooler:
execution guards use PostgreSQL session advisory locks. Each CLI invocation uses
a small pool; running a harness also holds a dedicated guard connection.

Use the same `project` value across trusted workspaces to share evidence. This
namespace is **not authorization or tenant isolation**: anyone with access to the
database tables can access other projects. Separate database roles/databases or a
future authenticated managed API are needed for mutually untrusted users.

### Setup safety and recovery

`storage setup` is preview-first. Applying requires both `--apply` and
`--confirm-stopped`: stop all source writers and supervisors, including scheduled
jobs and other workspaces using the source file. Recover any active run records
before retrying; this command does not stop processes or recover runs for you.

Apply backs up the exact old config to a new `.jevia/config-backups/config-*.toml`
file, initializes/checks the destination, imports requested history transactionally,
then atomically replaces config **last**. Routing, privacy, cache, harness settings,
and unrelated TOML comments are preserved. Config permissions are retained;
new backups are private on Unix and added to local ignore rules. Backups are not
automatically removed. On Windows, protect the directory with appropriate ACLs.

Active, duplicate, malformed, unsupported, or conflicting imported records cause
failure. Identical destination records are skipped for safe retries. Setup does
not replace config on connection, schema, permission, or import failure. It
serializes other setup invocations and checks for changes to the source/config
before switching, but cannot prevent an editor or already-running supervisor
from writing: the stop-writers requirement is not optional.

The database and filesystem are **not one atomic transaction**. A failed setup
can leave an initialized database, a config backup, or (if the final config save
fails) imported records. Inspect config, keep the original JSONL and backup, and
retry only after reconciling concurrent changes. A crash after replacement may
mean config was already switched; verify with `jevia storage check`. Never blindly
restore old config once new work has written to the database, since histories can
diverge. No automatic synchronization, rollback deletion, or backend fallback is
performed.

Once configured, repeat the same setup command **without `--import-jsonl`** to
initialize/check the same target without rewriting config or adding another backup.
Old JSONL files may be stale, so importing them from an already-SQL project needs
the separate explicit `storage import-jsonl` command. Changing between SQL targets
or back to JSONL is not supported by guided setup; use explicit export/import and
review the configuration change yourself.
Changing the value of the PostgreSQL URL environment variable can independently
change the destination; setup cannot detect which database it previously named.

### Guarantees and current limits

- Database writes are transactional. Feedback history and its current outcome
  change together; project-scoped write locks prevent lost updates. Retention
  holds the same lock while saving recovery files; schedule it during a quiet period.
- Recent history and eligible evidence use indexed, bounded queries in append
  order. Complete versioned records preserve execution and feedback provenance.
- `storage check` verifies schema and CRUD permissions with a rolled-back probe;
  it does not insert fake evidence. `doctor`/`check` include this access check.
  SQL records are not individually decoded by the default access check.
- `storage check --deep` instead inspects the complete selected history without
  a write probe, automatic repair, Jev request, or cache access. JSONL validates
  record schemas and nonempty/unique run IDs under the shared history lock,
  streaming records while retaining an ID set (memory grows with the IDs).
  SQL uses 200-row pages in one consistent read snapshot to validate record
  schemas, indexed run IDs, learning flags against current evidence rules, and
  positive append ordinals bounded by the project's append counter. Gaps left
  by retention are valid; the counter need not equal the newest retained ordinal.
  Only the configured PostgreSQL project is inspected. Ordinary SQL writers can
  continue, though a long scan may delay database cleanup/WAL recycling.
- A deep-check failure exits nonzero and identifies the line or append-position
  where possible, without printing task text, IDs, or database credentials. Keep
  the current history and backups and investigate locally before making changes;
  no records or indexes are silently rewritten. Success means the inspected
  snapshot passed these logical checks, not that write permissions, physical
  database integrity, or task-outcome correctness were verified. Run the default
  access check separately when needed; database-native integrity checks and
  backups remain the operator's responsibility. Missing storage is not initialized.
- Database/record schema versions are checked; unknown versions are rejected.
  Driver errors are redacted and database operations have five-second timeouts.
  An outage never silently switches history back to local JSONL.
- Imports preview by default and commit all-or-nothing with `--apply`. Identical
  run IDs are skipped; conflicting records, duplicate source IDs, active runs,
  or malformed/unsupported records abort the import. Stop source writers first.
  Keep the unchanged source as your backup; no automatic bidirectional sync or
  background replication is provided.
- `storage import-jsonl` validates and streams the source into a private unnamed
  temporary file before taking a database write lock. It holds the source's shared
  JSONL lock during capture, then imports only the captured records in one database
  transaction. Later changes to the source are not included; preview and apply
  capture independently. Both modes need temporary disk space for the normalized
  history in the OS temporary directory (use protected directory ACLs on Windows).
  Memory grows with the run-ID set plus the largest record, not all task bodies;
  the ID set is released before database writes. Snapshot failures prevent writes;
  later read errors or conflicts roll back all inserts and their ordering counter.
  The temporary file is removed on close/process exit, not kept as a backup.
  Imports still normalize known fields and omit unknown additive fields. Large
  imports hold the project write lock until commit/rollback (SQLite serializes
  all database writers); this is not a resumable or chunk-committed import. Guided
  `storage setup --import-jsonl` still captures its migration source in memory.
- `jevia storage export --output .jevia/snapshot.jsonl` writes a new private
  snapshot in append order. It streams one JSONL record or a bounded SQL page at
  a time, rather than loading the entire history. JSONL holds its shared history
  lock; SQL uses one consistent read snapshot across all pages (PostgreSQL
  repeatable-read/read-only, SQLite WAL snapshot) without taking the project write
  lock. SQL changes committed after the snapshot begins appear in a later export,
  not partway through this one. Very large records still require memory, and a
  long SQL snapshot can delay database cleanup/WAL recycling.
- Export writes to a private temporary file beside the destination, validates all
  records, flushes/syncs the file, and publishes without overwriting any existing
  path, including symlinks. Handled read/write failures do not publish a partial
  destination. The parent directory is synced on Unix; if that final sync fails,
  the error says the complete file was already created. A process crash can leave
  a private `.jevia-export-*.tmp` file. Protect and ignore exports and their output
  directory; they may contain task text. On Windows, use appropriate directory
  ACLs. Exports normalize known record fields, omit unknown additive fields, and
  are logical record snapshots, not exact-byte or physical database backups.
- The decision cache and cache-miss coordination remain local. Every routing
  attempt fetches current eligible evidence before computing its cache key, so
  new shared outcomes invalidate affected decisions. Cross-machine request
  deduplication and offline write queues are not included.
- SQLite run locks sit beside the canonical database path. PostgreSQL guards
  block recovery while the supervisor's database session is alive. Because a
  lost session does not prove its subprocess stopped, PostgreSQL recovery also
  requires `jevia runs recover <id> --confirm-stopped` after inspecting and
  stopping the original supervisor and any surviving processes. Recovery is
  terminal and fences late writes from that supervisor; it never reruns work.
  There is no automatic lease expiration or remote process termination.
- `runs archive` supports all three backends with preview-first, backup-first
  retention (see below). `runs repair` remains JSONL-only: database integrity
  repair and physical backups require database-native tooling.
- Managed hosting, managed credentials, and a dashboard are not part of this
  integration. Data is stored locally or in the user's own database.

### Default JSONL data

Project configuration lives in `.jevia/config.toml` and is intended to be
reviewed and committed. Run history lives in `.jevia/runs.jsonl` and is ignored
by the project-local `.jevia/.gitignore` because prompts and outcomes may be
sensitive. Jevia coordinates concurrent readers and writers through the
ignored `.jevia/runs.lock` sidecar so parallel agents cannot overwrite one
another's evidence.

Routing decisions live in the ignored `.jevia/cache.jsonl` file and use the
same locking and atomic-replacement guarantees through `.jevia/cache.lock`.

By default Jevia stores task text in the selected backend so it can supply useful examples to
future decisions. Set `store_task_text = false` under `[privacy]` to retain only
routing metadata.

In JSONL mode, `jevia doctor` validates the complete history and reports malformed records
without deleting or rewriting them.

Malformed configuration, history, and cache diagnostics omit raw values, including
parser error chains. They report line/column positions where available and safe
configuration guidance. This does not redact deliberately requested run/task output
or stdout/stderr streamed by your configured harness and verifier.

### History maintenance

Both maintenance commands preview by default. Inspect the report before repeating
with `--apply`; `--json` provides counts and saved paths for automation.

```sh
jevia runs repair
jevia runs repair --apply
jevia runs archive --keep 1000
jevia runs archive --keep 1000 --apply
```

Repair is JSONL-only. It handles an incomplete, unterminated final JSON line after
an interrupted write, or a valid final record missing its newline. It refuses malformed middle
lines, complete invalid records, unsupported schemas, and duplicate IDs. It does
not guess at missing fields or rewrite individual outcomes.

Archival works with JSONL, SQLite, and PostgreSQL. It keeps the most recently
**appended** `--keep` eligible terminal records (minimum one), plus every active,
routed/pending, or legacy-unknown record. SQL also retains any record with an
execution owner, even if its recorded lifecycle appears terminal. This command
does not recover runs, stop processes, or infer that old work has finished.

Older eligible records move to a separate JSONL archive. They no longer appear in
`runs` or `stats`, accept feedback, or inform routing. Routing fetches current
evidence before cache lookup, so removing relevant evidence changes the cache key;
archival itself does not clear or rewrite the local decision cache. Choose a
retention window large enough for the evidence you need. This is explicit
maintenance, not automatic pruning.

All backends save recovery files under the invoking project's
`.jevia/history-backups/` and `.jevia/history-archives/` **on the CLI machine**,
including when PostgreSQL is remote. Apply recomputes its plan under the history
lock. Preview (and a no-op apply) removes no records and creates no recovery files.

- **JSONL:** repair and archival back up the exact original file before atomic
  replacement. Archived record bytes, including additive metadata, are preserved.
- **SQL:** archival holds the selected project's write lock, validates all records
  in bounded pages, and saves a full project-record snapshot plus an archive of
  the selected rows before any deletion. A private temporary journal bounds
  memory while deletion batches commit in one transaction. Saved JSON preserves
  additive fields but flattens physical line breaks into JSONL; it is not an
  exact-byte or physical database backup. SQL project/ordinal/owner metadata and
  other projects are not included. There is no database schema change.

SQL rejects malformed/unsupported records and mismatched indexed IDs, rechecks
the snapshot against its plan, and conditions deletions on the saved row values.
Jevia writers serialize with retention; long operations
can make other commands hit their database timeout. Coordinate external SQL
writers too, since they may bypass Jevia's project lock. A snapshot failure
prevents deletion. A deletion failure rolls back all batches and leaves any
finalized recovery files. If a connection fails while committing, the result may
be ambiguous: inspect active history and the reported files before retrying or
restoring. Never assume a failed response means nothing committed.

These directories are ignored local data and may contain sensitive prompts.
New snapshot files/directories use private permissions on Unix; on Windows,
protect the project directory with appropriate filesystem ACLs. Jevia syncs files
before deleting, and also syncs snapshot directories on Unix. Backups and archives
are never overwritten or automatically removed. Total disk use can increase;
SQL archival does not vacuum or compact the database.

For SQL restoration, first stop writers, export the current state to a new file,
and review the chosen archive before explicitly importing it:

```sh
jevia storage export --output .jevia/before-restore.jsonl
# Replace sql-runs-UUID.jsonl with the archive path printed by `runs archive`.
jevia storage import-jsonl --from .jevia/history-archives/sql-runs-UUID.jsonl
jevia storage import-jsonl --from .jevia/history-archives/sql-runs-UUID.jsonl --apply
```

Import keeps run IDs, outcomes, and known provenance, skips identical records,
and aborts on conflicts. It **appends restored records as newest**, not at their
original SQL positions, which can change the routing evidence window. Unknown
additive JSON fields remain in the archive but are not retained by typed import.
A full pre-archive snapshot can contain active records and is not suitable for
blind import. For JSONL restoration, stop writers and save current history before
manual replacement; an older backup would otherwise discard newer runs. SQL
snapshots do not replace a database-native disaster-recovery backup strategy.

## Repository structure

- `crates/jevia-core` contains configuration, typed API contracts, policy, and
  outcome records.
- `crates/jevia-cli` contains JSONL/SQLite/PostgreSQL persistence and terminal commands.
- `website` contains the Farm.js product site and getting-started guide.

Dashboard code does not belong in this repository. The managed dashboard is a
separate private project with a separate security boundary.

## Development

```bash
cargo fmt --all -- --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
```

Run the website separately:

```bash
cd website
pnpm install
pnpm dev
```

The site serves `/install.sh` and uses the current page's origin in its copyable
install command: localhost during development and the deployed domain in
production. The script downloads the pinned GitHub release binary, verifies its
SHA-256 checksum, and installs it to `~/.local/bin` by default. It does not need
Rust or Cargo and does not modify shell configuration. Run `pnpm test` in
`website` to check the installer without installing anything.

See [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution and commit
conventions and [docs/architecture.md](docs/architecture.md) for component
boundaries and routing invariants.

## License

MIT