car-runtime 0.48.0 → 0.50.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.
Files changed (2) hide show
  1. package/index.d.ts +171 -17
  2. package/package.json +1 -1
package/index.d.ts CHANGED
@@ -23,7 +23,6 @@
23
23
  * - `openSession`, `closeSession`, `registerPolicy(sessionId)` — use
24
24
  * `session.open` / `session.close` JSON-RPC methods
25
25
  * - `stateSnapshot`, `stateKeys` — daemon-side endpoints pending
26
- * - `removeModel` — daemon owns models_dir / models.json
27
26
  *
28
27
  * (`registerModel` was re-exposed in #39 — it now proxies to the
29
28
  * daemon's `models.register` JSON-RPC. See its docstring for
@@ -445,6 +444,12 @@ export class CarRuntime {
445
444
  syncStatus(requestJson: string): Promise<string>;
446
445
  /** `sync.append` — record an op on any surface: `{ surface, payload, scope? }` (B6). */
447
446
  syncAppend(requestJson: string): Promise<string>;
447
+ /** Store/load exact supervised-assistant checkpoints in the durable oplog. */
448
+ syncAssistantCheckpointPut(requestJson: string): Promise<string>;
449
+ syncAssistantCheckpointGet(requestJson: string): Promise<string>;
450
+ /** Append/load monotone supervised-action lifecycle records. */
451
+ syncAssistantActionPut(requestJson: string): Promise<string>;
452
+ syncAssistantActionGet(requestJson: string): Promise<string>;
448
453
  /** `sync.record_turn` — route a conversation turn through the oplog so `syncResume` is real (B6). */
449
454
  syncRecordTurn(requestJson: string): Promise<string>;
450
455
  /** `sync.record_intent` — write the leased-execution intent ledger; feeds the fence oracle (B6). */
@@ -474,15 +479,97 @@ export class CarRuntime {
474
479
  * Run one evolution cycle over the daemon's **live** signals — the
475
480
  * self-evolution governor's real executor (arXiv 2507.21046). `requestJson`
476
481
  * is `{ policy?, dry_run?, harness_baseline_metrics?,
477
- * harness_candidate_metrics? }`; the daemon plans over all five live
482
+ * harness_candidate_metrics?, harness_measure?, context_measure? }`; the daemon plans over all five live
478
483
  * components (Memory/Skills/Context from the engine, Harness from the event
479
484
  * log, Tools from connector health) and dispatches each `EvolveNow`
480
485
  * component: Memory → consolidate (sized by decide_maintenance), Skills →
481
486
  * evolve_skills over event-log failure traces, Harness → the HITL-gated
482
487
  * harness_evolution loop (pending approvals resolve via
483
- * `permission.approve`/`reject` by fingerprint); Context/Tools record
484
- * `not_executable`. Returns the cycle record JSON
485
- * `{ plan, steps, evolved, pending_approvals? }`.
488
+ * `permission.approve`/`reject` by fingerprint), Context → the
489
+ * `context_evolution` loop, which resolves each mutation either through the
490
+ * opt-in pre-activation grader (`context_measure`) or, for whatever that did
491
+ * not decide, the diagnose→approve→apply→measure→revert human path. Returns
492
+ * the cycle record JSON
493
+ * `{ plan, steps, evolved, out_of_scope, pending_approvals?, measurement? }`,
494
+ * where each step is `{ component, ran, applied, out_of_scope, outcome }`.
495
+ *
496
+ * **Context.** Diagnoses off the engine's own live conversation-layer
497
+ * saturation and lowers `MemgineConfig.conversation_keep_recent` (halved,
498
+ * floored at 2) so compaction summarizes more of the older turns. Every
499
+ * mutation is HITL-gated on the same shared durable `ApprovalLedger` as
500
+ * harness ones, under its own fingerprint namespace
501
+ * `context:<component>:<patch-digest>`, resolved by the same
502
+ * `permission.approve`/`reject`. There **is** a pre-activation regression
503
+ * gate, opt-in via `context_measure` (see below) — this doc comment used to
504
+ * say there was none, because the bench replayed a runtime with no memgine
505
+ * attached and never offered a `recall` tool; bench tasks may now declare a
506
+ * `memory:` fixture and are then replayed with a real memgine and the shipped
507
+ * `recall` tool, so the assembled context moves with the knob. A graded
508
+ * mutation promotes (`applied` with `governance: "promoted"`) or is rejected
509
+ * (`rejected_by_gate`) with no operator in the loop. On the human-approved
510
+ * path — and whenever no grade ran — the daemon measures the MARGIN after the
511
+ * apply: compact under the unchanged value for a baseline
512
+ * (`conversation_tokens_baseline`), apply, compact again, and revert unless
513
+ * the tokens fell below that baseline (`rolled_back`, not counted as
514
+ * applied; `rollback_failed` with `rollback_error` if even the revert did
515
+ * not take). Comparing against the baseline rather than the uncompacted
516
+ * layer is what stops the change being credited with savings compaction
517
+ * would have produced anyway. So context is **not** unattended out of the
518
+ * box; it becomes unattended for a given change only once that fingerprint
519
+ * has been approved — and since the ledger is daemon-wide and the
520
+ * fingerprint names the change, that approval covers the same change on
521
+ * every engine this daemon evolves. On the unattended cadence a falsified
522
+ * mutation then backs off exponentially per fingerprint (`in_backoff`)
523
+ * instead of being re-applied and re-reverted every tick. The step's
524
+ * `outcome` is a JSON string `{ mechanism: "context_evolution", mutations,
525
+ * applied, pending, details }`, each detail carrying `mutation`,
526
+ * `component`, `fingerprint`, `rationale` and one of `pending_approval` |
527
+ * `applied` | `rolled_back` | `rollback_failed` | `apply_failed` |
528
+ * `would_apply` | `in_backoff` | `rejected_by_operator` |
529
+ * `approved_no_patch` | `rejected_by_gate` | `measurement_failed` |
530
+ * `config_moved_during_measurement` (a graded promotion whose measured base
531
+ * was moved by something else while the replays ran — nothing applied, both
532
+ * values reported, no backoff). When `context_measure` was requested the
533
+ * summary also carries `context_measured: { status: "measured" |
534
+ * "skipped_dry_run", grade_attempts, model, split, split_seed }`.
535
+ *
536
+ * **Tools** is recorded as `out_of_scope` — a decision, not a failure.
537
+ * Connector remediation means re-running a connector's OAuth or credential
538
+ * exchange, an access change this loop holds no authority to perform;
539
+ * reconnect/re-auth stay operator actions via `connectors.*`. Such a step is
540
+ * `ran: true, applied: false, out_of_scope: true` with the reason in
541
+ * `outcome`, and the component appears in the top-level `out_of_scope`
542
+ * array (always present, empty when none). `ran: false` therefore means one
543
+ * thing only: the mechanism was invoked and errored.
544
+ *
545
+ * `harness_measure` `{ model, split?, held_in_fraction?, split_seed?,
546
+ * max_turns?, tasks_dir? }` opts into **in-daemon measurement**: the daemon
547
+ * replays the held-out split itself (once for the baseline under the live
548
+ * `HarnessConfig`, once per measurable mutation under that config plus the
549
+ * mutation's patch) and feeds the regression gate, so a cycle can promote or
550
+ * reject unattended. It is mutually exclusive with the two supplied-metrics
551
+ * params (sending both errors, naming both); `dry_run` measures nothing and
552
+ * reports `measurement.status = "skipped_dry_run"`; a build with no
553
+ * in-process evaluator installed errors rather than degrading to HITL;
554
+ * safety-affecting and patchless mutations are never measured; a failed
555
+ * replay reports `measurement_failed` and fabricates no metrics.
556
+ *
557
+ * `context_measure` takes the SAME request shape and opts into the **Context
558
+ * pillar's** pre-activation grader: two replays over the same split, one
559
+ * under the engine's live `MemgineConfig` and one under it plus the
560
+ * mutation's patch, graded on TASK outcomes by the same gate. The two params
561
+ * are not mutually exclusive with each other (different pillars, two
562
+ * independent measurements). `dry_run` performs no replay; a build with no
563
+ * evaluator installed errors; a patchless mutation is never measured; the
564
+ * unattended cadence never requests a grade at all, so an idle timer cannot
565
+ * start spending benchmark replays.
566
+ * `measurement` is TOP-LEVEL on the response (not only inside the harness
567
+ * step) and present whenever `harness_measure` was requested, in every
568
+ * shape it can end in — `measured` / `skipped_dry_run` /
569
+ * `measurement_failed` with the error. A replay is a paid side effect and
570
+ * the plan may legitimately never dispatch Harness, so a side effect
571
+ * reported only from that step is one a caller can be billed for and never
572
+ * see.
486
573
  */
487
574
  runEvolutionCycleLive(requestJson: string): Promise<string>;
488
575
 
@@ -861,14 +948,28 @@ export class CarRuntime {
861
948
  /** Download a model. Returns its local path. */
862
949
  pullModel(name: string): Promise<string>;
863
950
 
864
- /** Remove a downloaded model. */
865
- removeModel(name: string): void;
951
+ /** Remove only a receipt-backed CAR-managed artifact. Returns result JSON. */
952
+ removeModel(modelId: string): Promise<string>;
953
+
954
+ /** Adopt an already-usable local artifact into CAR ownership. */
955
+ adoptModel(modelId: string): Promise<string>;
956
+
957
+ /** Read the saved local-model resource policy and evaluated budget. */
958
+ modelResourcePolicyGet(): Promise<string>;
959
+
960
+ /** Persist an exact resource-policy JSON object. */
961
+ modelResourcePolicySet(policyJson: string): Promise<string>;
962
+
963
+ /** Evaluate one local model without downloading or loading it. */
964
+ modelPreflight(modelId: string, contextTokens?: number): Promise<string>;
866
965
 
867
966
  /**
868
967
  * Unified registry (local + remote). Returns JSON array of
869
968
  * `{ id, name, provider, capabilities, param_count, size_mb,
870
- * context_length, available, is_local, weights_ready, downloads_weights,
871
- * max_output_tokens, public_benchmarks, cost }`. `available` means CAR
969
+ * context_length, available, is_local, operator_managed_external_runtime,
970
+ * weights_ready, downloads_weights,
971
+ * max_output_tokens, public_benchmarks, cost, car_enabled, can_remove,
972
+ * in_use, management_evidence }`. `available` means CAR
872
973
  * can use the model
873
974
  * here — for a local MLX entry with a declared `hf_repo` it is `true`
874
975
  * before a byte is fetched, because it lazy-downloads on first use —
@@ -876,13 +977,17 @@ export class CarRuntime {
876
977
  * models, having none to install, report `true`). Older daemons omit
877
978
  * `weights_ready`; it defaults to `false` rather than failing.
878
979
  * `downloads_weights` is `true` only for entries whose weights CAR fetches
879
- * before use (GGUF, MLX, whisper.cpp); when it is `false` — OS-provided
880
- * models such as `windows/speech-synthesis:os` and
881
- * `apple/foundation:default`, server-backed local models such as
882
- * `vllm-mlx/*` and Ollama, and every remote entry — there is nothing to
980
+ * before use (GGUF, MLX, whisper.cpp, and CAR-owned managed vLLM-MLX).
981
+ * When it is `false` — OS-provided models such as
982
+ * `windows/speech-synthesis:os` and `apple/foundation:default`,
983
+ * operator-managed servers such as raw vLLM-MLX and Ollama, and every
984
+ * remote entry — there is nothing to
883
985
  * install, so `weights_ready` is meaningless and the CLI renders
884
- * `INSTALLED` as `-`. Do not substitute `is_local`: those first four are
885
- * all local and all download nothing. Older daemons omit
986
+ * `INSTALLED` as `-`. Do not substitute `is_local`: OS-provided models are
987
+ * local but download nothing. A raw external vLLM-MLX row instead
988
+ * sets `operator_managed_external_runtime=true` and is not local, even for
989
+ * a loopback endpoint; only CAR-owned managed vLLM-MLX is charged and
990
+ * supervised as local. Older daemons omit
886
991
  * `downloads_weights`; it defaults to `false` rather than failing.
887
992
  * `max_output_tokens` is the registry-declared
888
993
  * per-model output ceiling (`null` when the entry omits it; callers
@@ -1558,11 +1663,60 @@ export class CarRuntime {
1558
1663
  mailAccounts(): string;
1559
1664
 
1560
1665
  /**
1561
- * Returns JSON inbox snapshot. `accountIdsCsv` is an optional
1562
- * comma-separated filter; omit to query all known accounts.
1666
+ * Returns JSON inbox snapshot
1667
+ * `{ available, backend, reason?, summaries: InboxSummary[] }` — per-account
1668
+ * unread/total counts, not message rows. Use `mailMessages` for rows.
1669
+ * `accountIdsCsv` is an optional comma-separated filter; omit to query all
1670
+ * known accounts.
1563
1671
  */
1564
1672
  mailInbox(accountIdsCsv?: string | null): string;
1565
1673
 
1674
+ /**
1675
+ * Enumerate every mailbox (folder) of the given accounts, nested ones
1676
+ * included on BOTH backends. Returns
1677
+ * `{ available, backend, reason?, mailboxes: Mailbox[] }` where `Mailbox`
1678
+ * is `{ account_id, name, full_name, unread, total }`.
1679
+ *
1680
+ * `full_name` is the selector to pass back as `MessageQuery.mailbox` — the
1681
+ * slash-joined path on macOS, the folder id on Microsoft Graph. Graph's
1682
+ * `/me/mailFolders` is root-only, so nested folders come from a bounded
1683
+ * `childFolders` walk (depth 8, at most 64 requests); a tree deeper or
1684
+ * wider than that is truncated.
1685
+ *
1686
+ * An `accountIdsCsv` that matches no account returns `available: false`
1687
+ * with a reason, not an empty list.
1688
+ */
1689
+ mailMailboxes(accountIdsCsv?: string | null): string;
1690
+
1691
+ /**
1692
+ * Read message rows, newest first. `queryJson` is a `MessageQuery`:
1693
+ * `{account_ids?: string[], mailbox?: string | null, limit?: number,
1694
+ * since?: string, include_body?: boolean}`. Every field defaults, and
1695
+ * `mailbox: null` means INBOX — so `"{}"` reproduces the pre-existing
1696
+ * INBOX-only read.
1697
+ *
1698
+ * "Newest first" is GLOBAL, not per account: rows from every matched
1699
+ * account are merged into one date-ordered list before `limit` applies, so
1700
+ * `limit: 1` across two accounts returns the newer message rather than
1701
+ * whichever account the backend listed first.
1702
+ *
1703
+ * Returns `{ available, backend, reason?, messages: MessageSummary[] }`;
1704
+ * each row carries a stable opaque `id` accepted by `mailMessageBody`, and
1705
+ * a `mailbox` holding the mailbox as the backend RESOLVED it (a query for
1706
+ * `"travel"` comes back stamped `"Travel/2026"`), so rows match
1707
+ * `mailMailboxes` output. An unresolvable mailbox or an unmatched
1708
+ * `account_ids` returns `available: false` with a reason, never an empty
1709
+ * list.
1710
+ */
1711
+ mailMessages(queryJson: string): string;
1712
+
1713
+ /**
1714
+ * Fetch one message body by the `id` from a `mailMessages` row. Returns
1715
+ * `{ available, backend, reason?, id, content_type, body, truncated }`;
1716
+ * bodies are cut at 100,000 characters with `truncated: true`.
1717
+ */
1718
+ mailMessageBody(messageId: string): string;
1719
+
1566
1720
  /**
1567
1721
  * Send mail. `sendRequestJson` is `{to, subject, body, ...}` per the
1568
1722
  * provider contract. Returns JSON `{ok, message_id?}`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "car-runtime",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "Common Agent Runtime — a deterministic execution layer for AI agents",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",