@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/cli/config-file.d.ts +22 -1
- package/dist/cli/config-file.js +42 -0
- package/dist/cli/main.d.ts +2 -1
- package/dist/cli/main.js +4 -6
- package/dist/cli/resume.js +1 -0
- package/dist/cli/run-history.d.ts +3 -7
- package/dist/cli/run-history.js +1 -10
- package/dist/cli/run-output.d.ts +16 -10
- package/dist/cli/run-output.js +19 -13
- package/dist/cli/ticket.js +3 -2
- package/dist/decider.d.ts +176 -64
- package/dist/decider.js +475 -155
- package/dist/executor.d.ts +49 -6
- package/dist/executor.js +456 -89
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/journal.d.ts +33 -4
- package/dist/journal.js +36 -6
- package/dist/schema.d.ts +189 -241
- package/dist/schema.js +56 -36
- package/dist/types.d.ts +49 -3
- package/dist/types.js +22 -0
- package/dist/when.d.ts +8 -1
- package/dist/when.js +8 -1
- package/dist/workflow-builder.js +3 -1
- package/dist/workflows/browser.js +2 -2
- package/dist/workflows/implement.yml +37 -5
- package/dist/workflows/triage.yml +40 -6
- package/package.json +1 -1
package/dist/decider.d.ts
CHANGED
|
@@ -1,32 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Decision models (#357)
|
|
2
|
+
* Decision models (#357): a fast typed classifier that decides routes.
|
|
3
3
|
*
|
|
4
4
|
* A System One decision model (Ollama `/v1/systemone`, TypeSafe Jev) answers
|
|
5
|
-
* one
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* one Choice question over a state in milliseconds. For a node that sets
|
|
6
|
+
* `route_by: decider` and whose conditional out-edges are natural language,
|
|
7
|
+
* the route ladder is:
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* 1. `when` expressions: sweny evaluates them, no model call (executor.ts).
|
|
10
|
+
* 2. The decider: its answer is the route only when the label is one of the
|
|
11
|
+
* node's live out-edges, confidence is at least `min_confidence` and the
|
|
12
|
+
* top label leads the runner-up by at least `min_margin`. Then the agent
|
|
13
|
+
* is never asked.
|
|
14
|
+
* 3. The agent's route evaluation, exactly as without a decider.
|
|
15
|
+
*
|
|
16
|
+
* Any decider problem (timeout, HTTP error, malformed answer, unknown label,
|
|
17
|
+
* low confidence or margin, no routable state, open breaker, spent cap) falls
|
|
18
|
+
* through to rung 3.
|
|
19
|
+
*
|
|
20
|
+
* Trust boundary: the endpoint, model and key come only from operator config
|
|
21
|
+
* (`.sweny.yml` or SWENY_DECIDER_* env), never from the workflow, and the
|
|
22
|
+
* URL must not point at a private, link-local or metadata address unless the
|
|
23
|
+
* operator allows private addresses. The state sent is an explicit projection
|
|
24
|
+
* of declared typed fields (see executor.ts), redacted and fenced. What this
|
|
25
|
+
* module logs is metadata only (edge target ids, numbers and a hash).
|
|
12
26
|
*/
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
/** ... and only when the top label leads the runner-up by at least this much. */
|
|
16
|
-
export declare const DECIDER_MIN_MARGIN = 0.2;
|
|
17
|
-
/** Per-call timeout when the provider does not set one. */
|
|
27
|
+
import dns from "node:dns";
|
|
28
|
+
/** Per-call timeout. */
|
|
18
29
|
export declare const DECIDER_DEFAULT_TIMEOUT_MS = 2000;
|
|
19
|
-
|
|
20
|
-
|
|
30
|
+
/** Consecutive failed calls that open the breaker for the rest of the run. */
|
|
31
|
+
export declare const DECIDER_BREAKER_FAILURES = 3;
|
|
32
|
+
/** Decider calls one logical run (resumes included) may make. */
|
|
33
|
+
export declare const DECIDER_DEFAULT_MAX_CALLS = 200;
|
|
34
|
+
/** Probabilities must sum to 1 within this. */
|
|
35
|
+
export declare const DECIDER_SUM_TOLERANCE = 0.01;
|
|
36
|
+
/** Workflow-level `decider` block: thresholds only. */
|
|
21
37
|
export interface DeciderConfig {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
38
|
+
/** Default 0.85, never below 0.7. */
|
|
39
|
+
min_confidence?: number;
|
|
40
|
+
/** Default 0.2, never below 0.1. */
|
|
41
|
+
min_margin?: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Operator config: where the decision model lives. Never read from a
|
|
45
|
+
* workflow file. The CLI builds it with one trust domain per credential: a
|
|
46
|
+
* URL from operator env (SWENY_DECIDER_URL) may be remote and gets the key
|
|
47
|
+
* (SWENY_DECIDER_API_KEY); a URL from repo content (`.sweny.yml`) is
|
|
48
|
+
* `loopbackOnly` and never gets a key. Library callers are the operator.
|
|
49
|
+
*/
|
|
50
|
+
export interface DeciderOperatorConfig {
|
|
51
|
+
/** Server root serving POST /v1/systemone. http or https only. */
|
|
52
|
+
url: string;
|
|
53
|
+
model: string;
|
|
54
|
+
/** Bearer key. Never sent when `loopbackOnly`. */
|
|
55
|
+
apiKey?: string;
|
|
56
|
+
/** Allow loopback and private addresses. Link-local and metadata addresses stay refused. */
|
|
57
|
+
allowPrivate?: boolean;
|
|
58
|
+
/** The URL came from repo content: it must resolve to loopback only, and no key is ever sent. */
|
|
59
|
+
loopbackOnly?: boolean;
|
|
60
|
+
/** DNS lookup to resolve the host with (test seam). Default: `dns.lookup`. */
|
|
61
|
+
lookup?: LookupFn;
|
|
62
|
+
/** Per-call timeout in ms. Default 2000. */
|
|
63
|
+
timeoutMs?: number;
|
|
64
|
+
/** Calls per logical run. Default 200. */
|
|
65
|
+
maxCalls?: number;
|
|
30
66
|
}
|
|
31
67
|
/** One Choice question: pick a key of `criteria`. */
|
|
32
68
|
export interface ChoiceAsk {
|
|
@@ -34,16 +70,16 @@ export interface ChoiceAsk {
|
|
|
34
70
|
instructions: string;
|
|
35
71
|
criteria: Record<string, string>;
|
|
36
72
|
}
|
|
37
|
-
/** Only Choice exists today. Noul and Score arrive as further union members. */
|
|
38
73
|
export type Ask = ChoiceAsk;
|
|
39
74
|
export interface ChoiceVerdict {
|
|
40
75
|
kind: "choice";
|
|
41
76
|
label: string;
|
|
77
|
+
/** Exactly the criteria keys (null prototype). */
|
|
42
78
|
probabilities: Record<string, number>;
|
|
43
79
|
confidence: number;
|
|
44
80
|
}
|
|
45
81
|
export type Verdict = ChoiceVerdict;
|
|
46
|
-
export type DecideErrorCode = "timeout" | "aborted" | "network" | "unauthorized" | "invalid_request" | "rate_limited" | "overloaded" | "http_error" | "malformed" | "unknown_label";
|
|
82
|
+
export type DecideErrorCode = "timeout" | "aborted" | "network" | "unauthorized" | "invalid_request" | "rate_limited" | "overloaded" | "http_error" | "malformed" | "unknown_label" | "blocked_address";
|
|
47
83
|
export declare class DecideError extends Error {
|
|
48
84
|
readonly code: DecideErrorCode;
|
|
49
85
|
constructor(code: DecideErrorCode, message: string);
|
|
@@ -61,84 +97,160 @@ export interface DecisionProvider {
|
|
|
61
97
|
/** Throws {@link DecideError}; never returns a partial answer. */
|
|
62
98
|
decide(req: DecideRequest): Promise<Record<string, Verdict>>;
|
|
63
99
|
}
|
|
100
|
+
/** Which addresses a decider connection may reach. */
|
|
101
|
+
export interface AddressPolicy {
|
|
102
|
+
/** Loopback and private ranges allowed (operator env only). */
|
|
103
|
+
allowPrivate?: boolean;
|
|
104
|
+
/** Only loopback allowed (a URL that came from repo content). */
|
|
105
|
+
loopbackOnly?: boolean;
|
|
106
|
+
}
|
|
107
|
+
/** Why connecting to this resolved address is refused, or null. */
|
|
108
|
+
export declare function addressProblem(address: string, policy: AddressPolicy): string | null;
|
|
109
|
+
/**
|
|
110
|
+
* Static checks on the operator's decider URL, or null when it may be tried:
|
|
111
|
+
* http(s) only, no credentials in it, no metadata host name, and an IP
|
|
112
|
+
* literal must pass {@link addressProblem}. A host name is checked again on
|
|
113
|
+
* every connection, against the addresses it resolves to.
|
|
114
|
+
*/
|
|
115
|
+
export declare function deciderUrlProblem(raw: string, policy?: AddressPolicy): string | null;
|
|
116
|
+
/** Node's `lookup` shape for `net.connect` / `http.request`. */
|
|
117
|
+
export type LookupFn = (hostname: string, options: dns.LookupOptions, callback: (err: NodeJS.ErrnoException | null, address: string | dns.LookupAddress[], family?: number) => void) => void;
|
|
118
|
+
/**
|
|
119
|
+
* A `lookup` for `http.request` that resolves every address, refuses the
|
|
120
|
+
* connection when any of them breaks the policy, and hands the socket the
|
|
121
|
+
* validated address itself. The socket connects to what was checked, so a
|
|
122
|
+
* second resolution cannot rebind the name to another address.
|
|
123
|
+
*/
|
|
124
|
+
export declare function guardedLookup(policy: AddressPolicy, base?: LookupFn): LookupFn;
|
|
64
125
|
export interface SystemOneOptions {
|
|
65
126
|
baseUrl: string;
|
|
66
127
|
model: string;
|
|
67
128
|
apiKey?: string;
|
|
68
129
|
/** Default timeout when `decide()` is not given one. Default 2000 ms. */
|
|
69
130
|
timeoutMs?: number;
|
|
131
|
+
/** Addresses the connection may reach. Default: public only. */
|
|
132
|
+
policy?: AddressPolicy;
|
|
133
|
+
/** DNS lookup the guard resolves with (test seam). Default: `dns.lookup`. */
|
|
134
|
+
lookup?: LookupFn;
|
|
70
135
|
}
|
|
71
136
|
/**
|
|
72
137
|
* `POST {baseUrl}/v1/systemone`. Ollama (local) and TypeSafe Jev (hosted)
|
|
73
138
|
* share this wire shape; only base URL, key and model differ. Response
|
|
74
139
|
* validation is strict: any deviation is a `malformed` or `unknown_label`
|
|
75
|
-
* error, never a guess.
|
|
140
|
+
* error, never a guess. Error messages carry no URL and no key.
|
|
76
141
|
*/
|
|
77
142
|
export declare function systemOneProvider(opts: SystemOneOptions): DecisionProvider;
|
|
78
|
-
|
|
143
|
+
/**
|
|
144
|
+
* Strict: `probabilities` holds exactly the criteria keys, each finite in
|
|
145
|
+
* [0, 1], summing to 1 within {@link DECIDER_SUM_TOLERANCE}; the chosen label
|
|
146
|
+
* is a criteria key, compared exactly (no normalization), and the unique
|
|
147
|
+
* maximum. Anything else throws.
|
|
148
|
+
*/
|
|
149
|
+
export declare function parseAnswers(body: unknown, asks: Record<string, Ask>): Record<string, Verdict>;
|
|
150
|
+
export type FallThroughReason = DecideErrorCode | "low_confidence" | "low_margin" | "label_outside_edge_set" | "no_routable_state";
|
|
151
|
+
export interface DeciderThresholds {
|
|
152
|
+
minConfidence: number;
|
|
153
|
+
minMargin: number;
|
|
154
|
+
}
|
|
79
155
|
export interface GateResult {
|
|
80
156
|
accepted: boolean;
|
|
81
157
|
reason?: FallThroughReason;
|
|
82
158
|
/** Top probability minus the runner-up (0 when there is no runner-up). */
|
|
83
159
|
margin: number;
|
|
84
160
|
}
|
|
85
|
-
/**
|
|
86
|
-
export declare function
|
|
87
|
-
/**
|
|
161
|
+
/** The workflow's thresholds, else the defaults, never looser than the floors, never above 1. */
|
|
162
|
+
export declare function resolveThresholds(config?: DeciderConfig): DeciderThresholds;
|
|
163
|
+
/** All gates must pass: label in the edge set, confidence, then top-two margin (each with float slack). */
|
|
164
|
+
export declare function gateVerdict(v: ChoiceVerdict, edgeSet: ReadonlySet<string>, thresholds?: DeciderThresholds): GateResult;
|
|
165
|
+
/** One decider consultation. Metadata only: no state, no prompts, no prose. */
|
|
88
166
|
export interface DeciderRecord {
|
|
89
167
|
/** Node the route was decided at. */
|
|
90
168
|
node: string;
|
|
169
|
+
/** `decided`: the label is the route. `fell_through`: the agent was asked. `skipped`: no call was made. */
|
|
170
|
+
outcome: "decided" | "fell_through" | "skipped";
|
|
171
|
+
reason?: FallThroughReason;
|
|
91
172
|
/** Edge target the decider picked (a node id). Null when it gave no answer. */
|
|
92
|
-
|
|
93
|
-
/** Edge target the agent picked. Null when the agent's evaluation failed. */
|
|
94
|
-
agent_label: string | null;
|
|
173
|
+
label: string | null;
|
|
95
174
|
confidence: number | null;
|
|
96
175
|
margin: number | null;
|
|
97
|
-
/** `compared`: the decider passed every gate. `fell_through`: it did not, see `reason`. */
|
|
98
|
-
outcome: "compared" | "fell_through";
|
|
99
|
-
reason?: FallThroughReason | "agent_failed";
|
|
100
|
-
/** Decider label equals agent label. Null unless `outcome` is `compared`. */
|
|
101
|
-
agree: boolean | null;
|
|
102
176
|
latency_ms: number;
|
|
103
177
|
model: string;
|
|
104
178
|
/** First 16 hex chars of sha256 over the state and criteria sent. */
|
|
105
179
|
input_hash: string;
|
|
106
180
|
}
|
|
107
|
-
/**
|
|
108
|
-
export interface
|
|
109
|
-
|
|
110
|
-
|
|
181
|
+
/** The breaker and cap counters, journaled so they span resumes. */
|
|
182
|
+
export interface DeciderCounters {
|
|
183
|
+
calls: number;
|
|
184
|
+
failures: number;
|
|
185
|
+
open: boolean;
|
|
111
186
|
}
|
|
112
187
|
export declare function hashDecisionInput(state: unknown, criteria: Record<string, string>): string;
|
|
113
|
-
export interface
|
|
114
|
-
|
|
115
|
-
settled: Promise<Omit<DeciderRecord, "agent_label" | "agree" | "node">>;
|
|
116
|
-
edgeSet: ReadonlySet<string>;
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* Start the decider call. Returns at once; the promise never rejects, so the
|
|
120
|
-
* agent's own evaluation is never delayed or failed by it.
|
|
121
|
-
*/
|
|
122
|
-
export declare function startShadowDecision(provider: DecisionProvider, req: {
|
|
188
|
+
export interface RouteQuestion {
|
|
189
|
+
node: string;
|
|
123
190
|
question: string;
|
|
191
|
+
/** The fenced, redacted routing projection. */
|
|
124
192
|
state: unknown;
|
|
193
|
+
/** Every live out-edge target of the node, with its condition. */
|
|
125
194
|
choices: {
|
|
126
195
|
id: string;
|
|
127
196
|
description: string;
|
|
128
197
|
}[];
|
|
129
198
|
signal?: AbortSignal;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
199
|
+
}
|
|
200
|
+
export interface RunDeciderOptions {
|
|
201
|
+
/** Raw workflow thresholds; clamped to the floors here. */
|
|
202
|
+
config?: DeciderConfig;
|
|
203
|
+
maxCalls?: number;
|
|
204
|
+
breakerFailures?: number;
|
|
205
|
+
timeoutMs?: number;
|
|
206
|
+
/** Counters carried over from an earlier attempt of the same run (resume). */
|
|
207
|
+
counters?: DeciderCounters;
|
|
208
|
+
/** Operator-facing messages (breaker opened, cap reached). Metadata only. */
|
|
209
|
+
warn?: (msg: string) => void;
|
|
210
|
+
/** Per-call log line. Metadata only. */
|
|
211
|
+
info?: (msg: string, data?: Record<string, unknown>) => void;
|
|
212
|
+
}
|
|
139
213
|
/**
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
214
|
+
* The decider for one logical run: thresholds, a circuit breaker and a call
|
|
215
|
+
* cap. `decide()` returns the edge target to take, or null to ask the agent.
|
|
216
|
+
* It never throws.
|
|
143
217
|
*/
|
|
144
|
-
export declare
|
|
218
|
+
export declare class RunDecider {
|
|
219
|
+
readonly provider: DecisionProvider;
|
|
220
|
+
private readonly opts;
|
|
221
|
+
readonly records: DeciderRecord[];
|
|
222
|
+
readonly thresholds: DeciderThresholds;
|
|
223
|
+
/** Set when the breaker opened or the cap was reached. */
|
|
224
|
+
offReason?: string;
|
|
225
|
+
private calls;
|
|
226
|
+
private failures;
|
|
227
|
+
private open;
|
|
228
|
+
private capped;
|
|
229
|
+
private readonly maxCalls;
|
|
230
|
+
private readonly breakerFailures;
|
|
231
|
+
private readonly timeoutMs;
|
|
232
|
+
constructor(provider: DecisionProvider, opts?: RunDeciderOptions);
|
|
233
|
+
get counters(): DeciderCounters;
|
|
234
|
+
/** Record that a route skipped the decider without a call (no HTTP). */
|
|
235
|
+
skip(node: string, reason: FallThroughReason): void;
|
|
236
|
+
decide(q: RouteQuestion): Promise<string | null>;
|
|
237
|
+
private log;
|
|
238
|
+
}
|
|
239
|
+
/** True when some node opts into the decider. */
|
|
240
|
+
export declare function wantsDecider(nodes: Record<string, {
|
|
241
|
+
route_by?: string;
|
|
242
|
+
}>): boolean;
|
|
243
|
+
/**
|
|
244
|
+
* Resolve the run's decider. `decider` is null with `off` set to the reason
|
|
245
|
+
* when the workflow opted nodes in but the decider cannot run; both are empty
|
|
246
|
+
* when no node opted in. No hosted fallback exists: null means no HTTP.
|
|
247
|
+
*/
|
|
248
|
+
export declare function createRunDecider(operator: DeciderOperatorConfig | false | undefined, workflow: {
|
|
249
|
+
decider?: DeciderConfig;
|
|
250
|
+
nodes: Record<string, {
|
|
251
|
+
route_by?: string;
|
|
252
|
+
}>;
|
|
253
|
+
}, opts?: Omit<RunDeciderOptions, "config" | "maxCalls" | "timeoutMs">): {
|
|
254
|
+
decider: RunDecider | null;
|
|
255
|
+
off?: string;
|
|
256
|
+
};
|