@sweny-ai/core 0.1.177 → 0.1.178

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.
package/dist/index.d.ts CHANGED
@@ -27,8 +27,8 @@ export { EVALUATOR_KINDS, EVAL_POLICIES, REQUIRES_ON_FAIL, NODE_ON_FAIL, MCP_TRA
27
27
  export { consoleLogger } from "./types.js";
28
28
  export { execute, RouteEvaluationError } from "./executor.js";
29
29
  export type { ExecuteOptions } from "./executor.js";
30
- export { systemOneProvider, DecideError, gateVerdict, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN } from "./decider.js";
31
- export type { DecisionProvider, DeciderConfig, DeciderMode, DeciderRecord, SystemOneOptions } from "./decider.js";
30
+ export { systemOneProvider } from "./decider.js";
31
+ export type { DeciderConfig, DeciderOperatorConfig } from "./decider.js";
32
32
  export { BudgetGuard, describeOverrun, minLimits, toLimits } from "./budget.js";
33
33
  export type { Budget, BudgetOverrun, BudgetUnit, SpendLimits } from "./budget.js";
34
34
  export { resolveNodePermissions, buildNodePolicy } from "./node-policy.js";
package/dist/index.js CHANGED
@@ -27,8 +27,8 @@ export { EVALUATOR_KINDS, EVAL_POLICIES, REQUIRES_ON_FAIL, NODE_ON_FAIL, MCP_TRA
27
27
  export { consoleLogger } from "./types.js";
28
28
  // Executor
29
29
  export { execute, RouteEvaluationError } from "./executor.js";
30
- // Decision models, shadow mode (#357)
31
- export { systemOneProvider, DecideError, gateVerdict, DECIDER_MIN_CONFIDENCE, DECIDER_MIN_MARGIN } from "./decider.js";
30
+ // Decision models (#357): config types and the provider adapter only
31
+ export { systemOneProvider } from "./decider.js";
32
32
  // Least-privilege nodes and safe outputs (#365)
33
33
  export { BudgetGuard, describeOverrun, minLimits, toLimits } from "./budget.js";
34
34
  export { resolveNodePermissions, buildNodePolicy } from "./node-policy.js";
package/dist/journal.d.ts CHANGED
@@ -29,7 +29,7 @@
29
29
  * output:intent a write is about to be applied (idempotency key + tool)
30
30
  * output:applied the write returned (key + the ids it produced)
31
31
  * node:end final result of the visit and the write-stage counters
32
- * route the edge taken after the visit (null = the run ended)
32
+ * route the edge taken after the visit (null = the run ended), and the rung that chose it
33
33
  * usage spend of one agent attempt (cumulative; live while it runs, final when it returns)
34
34
  * run:end success | failed | crashed
35
35
  *
@@ -52,9 +52,10 @@
52
52
  * (the process died between the API call and the receipt) is looked up on the
53
53
  * provider by a marker embedded in the body before anything is re-sent.
54
54
  */
55
- import type { Logger, NodeResult, NodeUsage, Skill, Workflow } from "./types.js";
55
+ import type { Logger, NodeResult, NodeUsage, RouteRung, Skill, Workflow } from "./types.js";
56
56
  import type { SafeOutputIntent, WriteStageState } from "./safe-outputs.js";
57
57
  import { type Spend } from "./budget.js";
58
+ import type { DeciderCounters } from "./decider.js";
58
59
  /** v2: records are authenticated with a per-run HMAC key (v1 used a public checksum and is refused). */
59
60
  export declare const JOURNAL_SCHEMA_VERSION = 2;
60
61
  export declare const JOURNAL_DIR: string;
@@ -139,6 +140,10 @@ export interface RunStartRecord extends JournalRecord {
139
140
  sweny_version?: string;
140
141
  input: unknown;
141
142
  input_redacted: boolean;
143
+ decider?: {
144
+ mode: "on" | "off";
145
+ reason?: string;
146
+ };
142
147
  }
143
148
  export interface WriteStateSnapshot {
144
149
  counts: Array<[string, number]>;
@@ -218,6 +223,8 @@ export interface PlannedVisit {
218
223
  status?: NodeResult["status"];
219
224
  /** Route taken after the visit: a node id, null for run end, undefined when not journaled. */
220
225
  next?: string | null;
226
+ /** Who chose that route, when it was a decision (#357). */
227
+ rung?: RouteRung;
221
228
  /** Writes the checkpointed write stage already applied (receipts in the journal). */
222
229
  applied: number;
223
230
  /** Writes with an intent but no receipt: checked on the provider before anything is re-sent. */
@@ -243,6 +250,8 @@ export interface ResumePlan {
243
250
  receipts: Map<string, OutputRecord>;
244
251
  /** Spend every earlier attempt journaled: the run budget starts here on resume. */
245
252
  priorSpend: Spend;
253
+ /** The decider's mode at run start and its counters after the last journaled route (#357). */
254
+ decider?: JournalDeciderState;
246
255
  }
247
256
  /**
248
257
  * Check that the records are a sequence the executor can write: one run:start
@@ -281,6 +290,21 @@ export interface JournalBeginInfo {
281
290
  config: Record<string, string>;
282
291
  writeState: WriteStageState;
283
292
  harnessId?: string;
293
+ /** Whether the run's decider ran (#357), journaled so a resume keeps the same mode. */
294
+ decider?: {
295
+ mode: "on" | "off";
296
+ reason?: string;
297
+ };
298
+ }
299
+ /**
300
+ * The decider as an earlier attempt left it (#357): its mode at run start and
301
+ * the breaker / cap counters after the last journaled route. Cap and breaker
302
+ * are per logical run, so a resume continues them.
303
+ */
304
+ export interface JournalDeciderState {
305
+ mode: "on" | "off";
306
+ reason?: string;
307
+ counters?: DeciderCounters;
284
308
  }
285
309
  export interface JournalCheckpoint {
286
310
  result: NodeResult;
@@ -292,6 +316,7 @@ export type JournalReplay = {
292
316
  kind: "complete";
293
317
  result: NodeResult;
294
318
  next?: string | null;
319
+ rung?: RouteRung;
295
320
  } | ({
296
321
  kind: "checkpoint";
297
322
  } & JournalCheckpoint);
@@ -308,7 +333,8 @@ export interface ExecutionJournal {
308
333
  /** The skill map the write stage applies through: write tools journaled and made idempotent. */
309
334
  wrapWrites(node: string, iteration: number, skills: Map<string, Skill>): Map<string, Skill>;
310
335
  nodeEnd(node: string, iteration: number, result: NodeResult, writeState: WriteStageState): void;
311
- route(from: string, to: string | null): void;
336
+ /** The edge taken after a visit, and for a decision the rung that chose it (#357), so resume replays it. */
337
+ route(from: string, to: string | null, rung?: RouteRung, decider?: DeciderCounters): void;
312
338
  /**
313
339
  * Spend of one agent attempt, cumulative for the attempt: `final` when the
314
340
  * attempt returned, otherwise a live report (the journal may throttle those).
@@ -316,6 +342,8 @@ export interface ExecutionJournal {
316
342
  usage?(node: string, iteration: number, attempt: number, usage: NodeUsage, final: boolean): void;
317
343
  /** Spend earlier attempts of this run already journaled (resume); the run budget starts from it. */
318
344
  priorSpend?(): Spend | undefined;
345
+ /** The decider state earlier attempts of this run journaled (resume); undefined otherwise. */
346
+ deciderResume?(): JournalDeciderState | undefined;
319
347
  }
320
348
  /** Minimum gap between two live usage records of one attempt. Final reports are always written. */
321
349
  export declare const USAGE_JOURNAL_INTERVAL_MS = 2000;
@@ -389,11 +417,12 @@ export declare class RunJournal implements ExecutionJournal {
389
417
  private closeFd;
390
418
  private clean;
391
419
  begin(info: JournalBeginInfo): void;
420
+ deciderResume(): JournalDeciderState | undefined;
392
421
  replay(node: string, iteration: number): JournalReplay | undefined;
393
422
  nodeStart(node: string, iteration: number): void;
394
423
  checkpoint(node: string, iteration: number, cp: JournalCheckpoint): void;
395
424
  nodeEnd(node: string, iteration: number, result: NodeResult, writeState: WriteStageState): void;
396
- route(from: string, to: string | null): void;
425
+ route(from: string, to: string | null, rung?: RouteRung, decider?: DeciderCounters): void;
397
426
  usage(node: string, iteration: number, attempt: number, usage: NodeUsage, final: boolean): void;
398
427
  priorSpend(): Spend | undefined;
399
428
  /** Close the run: `run:end`, then release the lock. Safe to call twice (one run:end). */
package/dist/journal.js CHANGED
@@ -29,7 +29,7 @@
29
29
  * output:intent a write is about to be applied (idempotency key + tool)
30
30
  * output:applied the write returned (key + the ids it produced)
31
31
  * node:end final result of the visit and the write-stage counters
32
- * route the edge taken after the visit (null = the run ended)
32
+ * route the edge taken after the visit (null = the run ended), and the rung that chose it
33
33
  * usage spend of one agent attempt (cumulative; live while it runs, final when it returns)
34
34
  * run:end success | failed | crashed
35
35
  *
@@ -293,13 +293,15 @@ export function redact(value, secrets = []) {
293
293
  return v.map((x) => walk(x, depth + 1));
294
294
  if (v && typeof v === "object") {
295
295
  const out = {};
296
+ // defineProperty, not assignment: a key named `__proto__` stays a plain key.
297
+ const put = (k, x) => Object.defineProperty(out, k, { value: x, enumerable: true, writable: true, configurable: true });
296
298
  for (const [k, x] of Object.entries(v)) {
297
299
  if (typeof x === "string" && isSecretKey(k) && x.length > 0) {
298
- out[k] = REDACTED;
300
+ put(k, REDACTED);
299
301
  redacted = true;
300
302
  }
301
303
  else if (x !== undefined && typeof x !== "function") {
302
- out[k] = walk(x, depth + 1);
304
+ put(k, walk(x, depth + 1));
303
305
  }
304
306
  }
305
307
  return out;
@@ -467,6 +469,13 @@ const RECORD_TYPES = new Set([
467
469
  const isPosInt = (v) => typeof v === "number" && Number.isInteger(v) && v > 0;
468
470
  const isName = (v) => typeof v === "string" && v.length > 0;
469
471
  const isAmount = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0;
472
+ const isRung = (v) => v === "expr" || v === "decider" || v === "agent";
473
+ const isCount = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0;
474
+ const isCounters = (v) => typeof v === "object" &&
475
+ v !== null &&
476
+ isCount(v.calls) &&
477
+ isCount(v.failures) &&
478
+ typeof v.open === "boolean";
470
479
  /**
471
480
  * Check that the records are a sequence the executor can write: one run:start
472
481
  * first; nothing but run:resume after run:end; a visit starts only where the
@@ -583,6 +592,10 @@ export function checkRecordSequence(records) {
583
592
  bad(r, `routes from ${String(r.from)}, but the visit that ended is ${cur.node}`);
584
593
  if (r.to !== null && !isName(r.to))
585
594
  bad(r, "has no valid target");
595
+ if (r.rung !== undefined && !isRung(r.rung))
596
+ bad(r, "has an unknown rung");
597
+ if (r.decider !== undefined && !isCounters(r.decider))
598
+ bad(r, "has invalid decider counters");
586
599
  cur.routed = true;
587
600
  next = r.to;
588
601
  break;
@@ -663,6 +676,7 @@ export function buildResumePlan(records) {
663
676
  const receipts = new Map();
664
677
  let attempts = 1;
665
678
  let lastStatus;
679
+ let deciderCounters;
666
680
  for (const r of records) {
667
681
  switch (r.type) {
668
682
  case "run:resume":
@@ -681,6 +695,7 @@ export function buildResumePlan(records) {
681
695
  // Re-run of a visit an earlier attempt did not finish (or finished failed).
682
696
  delete v.end;
683
697
  delete v.next;
698
+ delete v.rung;
684
699
  }
685
700
  break;
686
701
  }
@@ -699,8 +714,14 @@ export function buildResumePlan(records) {
699
714
  case "route": {
700
715
  const from = r.from;
701
716
  const v = [...order].reverse().find((x) => x.node === from);
702
- if (v)
717
+ if (v) {
703
718
  v.next = r.to ?? null;
719
+ if (isRung(r.rung))
720
+ v.rung = r.rung;
721
+ }
722
+ // Calls already made count even if the visit later re-runs.
723
+ if (isCounters(r.decider))
724
+ deciderCounters = r.decider;
704
725
  break;
705
726
  }
706
727
  case "output:intent":
@@ -733,6 +754,7 @@ export function buildResumePlan(records) {
733
754
  action: "replay",
734
755
  status: v.end.result.status,
735
756
  next: v.next,
757
+ ...(v.rung ? { rung: v.rung } : {}),
736
758
  applied,
737
759
  unconfirmed,
738
760
  });
@@ -776,6 +798,9 @@ export function buildResumePlan(records) {
776
798
  intents,
777
799
  receipts,
778
800
  priorSpend: journaledSpend(records),
801
+ ...(start.decider
802
+ ? { decider: { ...start.decider, ...(deciderCounters ? { counters: { ...deciderCounters } } : {}) } }
803
+ : {}),
779
804
  };
780
805
  }
781
806
  /**
@@ -1153,8 +1178,12 @@ export class RunJournal {
1153
1178
  ...(this.opts.swenyVersion ? { sweny_version: this.opts.swenyVersion } : {}),
1154
1179
  input: input.value,
1155
1180
  input_redacted: input.redacted,
1181
+ ...(info.decider ? { decider: info.decider } : {}),
1156
1182
  });
1157
1183
  }
1184
+ deciderResume() {
1185
+ return this.resume?.plan.decider;
1186
+ }
1158
1187
  replay(node, iteration) {
1159
1188
  const plan = this.resume?.plan;
1160
1189
  if (!plan)
@@ -1172,6 +1201,7 @@ export class RunJournal {
1172
1201
  kind: "complete",
1173
1202
  result: structuredClone(end.result),
1174
1203
  ...(visit.next !== undefined ? { next: visit.next } : {}),
1204
+ ...(visit.rung ? { rung: visit.rung } : {}),
1175
1205
  };
1176
1206
  }
1177
1207
  if (visit.action === "write-stage") {
@@ -1209,8 +1239,8 @@ export class RunJournal {
1209
1239
  write_state: { counts: [...writeState.counts], total: writeState.total, seen: [...writeState.seen] },
1210
1240
  });
1211
1241
  }
1212
- route(from, to) {
1213
- this.append("route", { from, to });
1242
+ route(from, to, rung, decider) {
1243
+ this.append("route", { from, to, ...(rung ? { rung } : {}), ...(decider ? { decider } : {}) });
1214
1244
  }
1215
1245
  usage(node, iteration, attempt, usage, final) {
1216
1246
  const s = spendOf(usage);