car-runtime 0.52.1 → 0.54.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.
@@ -168,6 +168,24 @@ Snake-case enum. What happens when this action's tool returns an error or a prec
168
168
  | `"retry"` | retry up to `max_retries` times before aborting |
169
169
  | `"skip"` | mark this action skipped and continue with the rest |
170
170
 
171
+ ### Tool failure classification
172
+
173
+ A tool executor may return a typed `ToolFailure` with classification
174
+ `"ordinary"` or `"terminal"`. This is evidence produced by the tool during
175
+ execution, not a fourth `FailureBehavior`:
176
+
177
+ - `"ordinary"` follows the action's declared failure behavior. Existing string
178
+ errors convert to this classification, so legacy errors keep their current
179
+ retry, skip, or abort behavior.
180
+ - `"terminal"` stops retrying immediately and aborts and rolls back the
181
+ proposal regardless of its declared failure behavior. The failed
182
+ `ActionResult` carries `"terminal": true`; the field is absent for all other
183
+ results and defaults to false when deserializing older results.
184
+
185
+ Terminality is strictly opt-in. CAR never infers it from words such as
186
+ "terminal" or "fatal" in an error message. This engine-level contract does not
187
+ halt a daemon session; session halting is a separate daemon-owned layer.
188
+
171
189
  ### Action lifecycle (informational)
172
190
 
173
191
  The runtime tags each action with an `ActionStatus` as it moves through validation and execution:
@@ -181,6 +199,35 @@ Proposed → Validated → Executing → Succeeded
181
199
 
182
200
  `ActionStatus` is observable through the event log, not part of the input contract.
183
201
 
202
+ #### Execution outcome event data
203
+
204
+ Every per-action `ActionFailed` event carries:
205
+
206
+ - `params_digest`: lowercase SHA-256 of the RFC 8785/JCS-canonicalized action
207
+ `parameters` object. The event never copies raw parameters; consumers join
208
+ through `proposal_id` + `action_id` to the authoritative `ProposalReceived`
209
+ record and can use the digest to detect a mismatch.
210
+ - `expected_effects`: the action's declared expected-effects object, unchanged.
211
+ - `error_class`: one of `timeout`, `rejected_by_policy`, `tool_error`,
212
+ `validation`, or `unknown`.
213
+
214
+ `ActionSucceeded` carries `params_digest` and `expected_effects` too, making the
215
+ success/failure join symmetric without adding an error classification to a
216
+ successful call.
217
+
218
+ The normalized error mapping is intentionally low-cardinality:
219
+
220
+ | `error_class` | Mapping |
221
+ |---|---|
222
+ | `timeout` | the engine's action deadline expired, or the daemon-to-host tool callback reported its own timeout |
223
+ | `rejected_by_policy` | a dispatch-time tool guard returned the stable `denied by policy:` or `rejected by policy:` prefix |
224
+ | `validation` | post-dispatch output or callback-state JCS/I-JSON, state-key-set, or serialization validation failed |
225
+ | `tool_error` | any other error returned while dispatching a tool action |
226
+ | `unknown` | a post-dispatch failure on an action with no tool |
227
+
228
+ Normal action/schema/policy admission failures happen before execution and are
229
+ `ActionRejected`, not `ActionFailed`, so this mapping does not reclassify them.
230
+
184
231
  ---
185
232
 
186
233
  ## Precondition
@@ -220,6 +267,7 @@ Registered when a tool is added to the runtime. Carries everything the runtime n
220
267
  ```jsonc
221
268
  {
222
269
  "name": "deploy",
270
+ "source": "user_defined",
223
271
  "description": "Deploys an artifact to a target environment.",
224
272
  "parameters": {
225
273
  "type": "object",
@@ -238,6 +286,7 @@ Registered when a tool is added to the runtime. Carries everything the runtime n
238
286
  | Field | Type | Required | Default | Notes |
239
287
  |-------|------|----------|---------|-------|
240
288
  | `name` | string | **yes** | — | unique within a runtime |
289
+ | `source` | `builtin \| user_defined \| subprocess \| mcp` | no | `user_defined` | stable origin category assigned by the runtime; MCP server detail remains registry-private |
241
290
  | `description` | string | no | `""` | human-readable; included in tool catalog |
242
291
  | `parameters` | JSON Schema | no | `{}` | validated by the runtime before dispatch |
243
292
  | `returns` | JSON Schema | no | none | validated against tool return value when set |
@@ -302,12 +351,10 @@ Returned by `proposal.submit` (WebSocket), `executeProposal` (NAPI), `execute_pr
302
351
  {
303
352
  "action_id": "a1",
304
353
  "status": "succeeded",
354
+ "rolled_back": true,
305
355
  "output": { "deployed": true },
306
- "error": null,
307
- "state_changes": {
308
- "deployed": { "op": "set", "value": true },
309
- "obsolete_key": { "op": "delete" }
310
- },
356
+ "error": "proposal aborted; state effects were rolled back; external effects may remain and were not undone",
357
+ "state_changes": {},
311
358
  "duration_ms": 1230.0,
312
359
  "timestamp": "2026-05-02T12:00:01Z"
313
360
  }
@@ -318,6 +365,19 @@ Returned by `proposal.submit` (WebSocket), `executeProposal` (NAPI), `execute_pr
318
365
 
319
366
  `status` is one of: `"proposed"`, `"validated"`, `"rejected"`, `"executing"`, `"succeeded"`, `"failed"`, `"skipped"`.
320
367
 
368
+ A failed action includes `"terminal": true` only when its tool returned
369
+ `ToolFailureClassification::Terminal`. The field is omitted otherwise. A
370
+ terminal result means the engine stopped retries and aborted this proposal; it
371
+ does not by itself describe daemon session state.
372
+
373
+ `rolled_back` is an independent commit marker. A successful action in an
374
+ aborted proposal remains `status: "succeeded"` because it executed, while
375
+ `rolled_back: true` reports that the enclosing state transaction was restored.
376
+ Its `state_changes` are empty and `error` may carry the warning that external
377
+ effects can remain. Consumers must use `rolled_back`, never compare that warning
378
+ text. The field defaults to `false` when absent and false values are omitted from
379
+ the serialized response for compatibility with older readers.
380
+
321
381
  Each `state_changes` value is a tagged `StateMutation`: `{"op":"set","value":…}`
322
382
  sets the key (including explicitly setting it to JSON `null`), while
323
383
  `{"op":"delete"}` removes it. Rust consumers can encode and decode that stable
@@ -562,15 +622,21 @@ allow = ["staging", "preview"] # any other target — or none at all — is de
562
622
  | `allow` | no | permitted values; defaults to empty, which denies every call |
563
623
 
564
624
  #### `deny_tool_param_matching`
565
- The content counterpart to `deny_tool_param`, for prohibitions no fixed substring expresses — credential shapes, account numbers, an address family. `matches` is a regex over the string-coerced parameter value. The match is **unanchored**, so the pattern fires anywhere in the value; anchor it with `^`/`$` when that matters. Like `deny_tool_param`, an absent parameter is not a violation — use `allow_tool_param` when absence itself must be refused.
625
+ The content counterpart to `deny_tool_param`, for conditions no fixed substring expresses — credential shapes, account numbers, an address family, or an open-ended trusted prefix. `matches` is a regex over the string-coerced parameter value. The match is **unanchored**, so the pattern fires anywhere in the value; anchor it with `^`/`$` when that matters.
566
626
 
567
- The pattern is compiled once when the rule set is applied, not per action. A pattern that fails to compile **denies every call to that tool** rather than disappearing, matching the loader's loud-error posture.
627
+ By default a regex match denies and an absent parameter does not. Set `negate = true` for the "unless" form: a mismatch denies, and an absent parameter also denies because nothing proves the required pattern. The pattern is compiled once when the rule set is applied, not per action. A pattern that fails to compile **denies every call to that tool** rather than disappearing, matching the loader's loud-error posture.
568
628
 
569
629
  ```toml
570
630
  [[deny_tool_param_matching]]
571
631
  tool = "http_request"
572
632
  param = "body"
573
633
  matches = "sk-[A-Za-z0-9]{20,}" # never let an API-key-shaped string leave in a body
634
+
635
+ [[deny_tool_param_matching]]
636
+ tool = "docker.rm"
637
+ param = "name"
638
+ matches = "^parslee-"
639
+ negate = true # deny unless the name has the trusted prefix
574
640
  ```
575
641
 
576
642
  | Param | Required | Notes |
@@ -578,6 +644,7 @@ matches = "sk-[A-Za-z0-9]{20,}" # never let an API-key-shaped string leave in
578
644
  | `tool` | yes | tool name the rule applies to |
579
645
  | `param` | yes | parameter key inspected on the action |
580
646
  | `matches` | yes | regex source; unanchored; an uncompilable pattern denies the tool outright |
647
+ | `negate` | no | defaults to `false`; when `true`, deny mismatch or absence instead of match |
581
648
 
582
649
  #### `rate_limit_tool`
583
650
  A sliding-window cap on how often `tool` may be called. The call is denied when admitting it would make it the `max_calls + 1`-th call to `tool` within the trailing `interval_secs`. `max_calls = 0` denies every call. This bounds how much of a side effect an agent can produce in a stretch of wall-clock time, independently of whether any single call is legitimate.