@inflexa-ai/harness 0.26.0 → 0.26.1
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/agents/report-session-agent.d.ts +5 -4
- package/dist/agents/report-session-agent.js +2 -2
- package/dist/lib/zod-issues.d.ts +9 -4
- package/dist/lib/zod-issues.js +35 -4
- package/dist/runtime/assemble.d.ts +3 -0
- package/dist/runtime/assemble.js +9 -1
- package/dist/sandbox/client.d.ts +9 -13
- package/dist/sandbox/client.js +8 -3
- package/dist/sandbox/create-sandbox.js +0 -1
- package/dist/tasks/derive-table-exec.d.ts +50 -0
- package/dist/tasks/derive-table-exec.js +84 -0
- package/dist/tools/report-session/derive-table.d.ts +39 -7
- package/dist/tools/report-session/derive-table.js +24 -83
- package/dist/tools/start-report-session.js +6 -2
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ import type { Pool } from "pg";
|
|
|
26
26
|
import type { AuthContext } from "../auth/types.js";
|
|
27
27
|
import type { RunAuthorizer } from "../execution/run-authorizer.js";
|
|
28
28
|
import type { AgentDefinition } from "../loop/types.js";
|
|
29
|
-
import type {
|
|
29
|
+
import type { DeriveTableRunner } from "../tools/report-session/derive-table.js";
|
|
30
30
|
import type { ReportSessionStateStore } from "../state/report-session-state.js";
|
|
31
31
|
import type { EmbeddingProvider } from "../providers/types.js";
|
|
32
32
|
import type { WorkspaceFilesystem } from "../workspace/filesystem.js";
|
|
@@ -65,10 +65,11 @@ export interface ReportSessionAgentDeps {
|
|
|
65
65
|
/** Derivation ledger -- the derivation tool appends one record for each derived table. */
|
|
66
66
|
readonly derivations: Pick<ReportSessionStateStore, "appendDerivation">;
|
|
67
67
|
/**
|
|
68
|
-
*
|
|
69
|
-
*
|
|
68
|
+
* Derivation-exec seam -- the derivation tool runs one ephemeral container for each derived table, and
|
|
69
|
+
* the composition realizes this seam over a registered workflow. Omitted, the tool reports that the
|
|
70
|
+
* composition gives no sandbox, and it derives nothing.
|
|
70
71
|
*/
|
|
71
|
-
readonly
|
|
72
|
+
readonly runDerivation?: DeriveTableRunner;
|
|
72
73
|
/**
|
|
73
74
|
* Run-authorization seam -- the derivation tool authorizes the exec and revokes on every terminal path.
|
|
74
75
|
* Omitted, the tool derives nothing, the same as an absent sandbox client.
|
|
@@ -39,7 +39,7 @@ const REPORT_SESSION_MAX_ITERATIONS = 75;
|
|
|
39
39
|
/** Build the report `AgentDefinition` with every tool bound to its deps. */
|
|
40
40
|
export function createReportSessionAgent(deps) {
|
|
41
41
|
const { model, pool, embedding, workspaceFs, gateway, resolveWorkspaceRoot, store, threads, chrome, eyes, makeResolver, resolvePageAsset, logger } = deps;
|
|
42
|
-
const { derivations,
|
|
42
|
+
const { derivations, runDerivation, runAuthorizer, makeSessionPages, resolvePageUrl } = deps;
|
|
43
43
|
const authoring = createReportAuthoringTools(gateway);
|
|
44
44
|
const tools = [
|
|
45
45
|
// The read surface toward the analysis. It roams the tree read-only.
|
|
@@ -73,7 +73,7 @@ export function createReportSessionAgent(deps) {
|
|
|
73
73
|
gateway,
|
|
74
74
|
resolveWorkspaceRoot,
|
|
75
75
|
derivations,
|
|
76
|
-
...(
|
|
76
|
+
...(runDerivation ? { runDerivation } : {}),
|
|
77
77
|
...(runAuthorizer ? { runAuthorizer } : {}),
|
|
78
78
|
...(logger ? { logger } : {}),
|
|
79
79
|
}),
|
package/dist/lib/zod-issues.d.ts
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
* caller's data; it distinguishes the two modes so the message the model
|
|
22
22
|
* reads matches the mistake it actually made. When neither mode explains the
|
|
23
23
|
* string it stays silent, because speculative advice to a model that is
|
|
24
|
-
* already misdiagnosing is worse than no advice at all.
|
|
24
|
+
* already misdiagnosing is worse than no advice at all. It also measures a
|
|
25
|
+
* string that broke a maximum length, because the Zod message gives the
|
|
26
|
+
* bound and never the length that arrived.
|
|
25
27
|
* - `repairToolInput` — an **issue-guided** repair: only a path where the
|
|
26
28
|
* schema wanted an `object` or `array` and found a string is even considered,
|
|
27
29
|
* so a tool that legitimately declares a `z.string()` field is never touched.
|
|
@@ -46,9 +48,12 @@ import type { z } from "zod";
|
|
|
46
48
|
* said. `input` is the raw value that failed validation — the same value handed
|
|
47
49
|
* to `safeParse` — because the issue alone does not carry the offending value.
|
|
48
50
|
*
|
|
49
|
-
* Fires
|
|
50
|
-
*
|
|
51
|
-
*
|
|
51
|
+
* Fires for a `too_big` issue on a string, which yields the length that arrived
|
|
52
|
+
* and the count to remove.
|
|
53
|
+
*
|
|
54
|
+
* Fires otherwise only for an `invalid_type` issue wanting an `object` or an
|
|
55
|
+
* `array` whose path resolves to a string, and only when that string
|
|
56
|
+
* demonstrably *is* the expected value:
|
|
52
57
|
*
|
|
53
58
|
* 1. it parses in full as JSON of the expected type → double encoding, and
|
|
54
59
|
* 2. it parses only once a wrapper artifact is stripped → the value is intact
|
package/dist/lib/zod-issues.js
CHANGED
|
@@ -21,7 +21,9 @@
|
|
|
21
21
|
* caller's data; it distinguishes the two modes so the message the model
|
|
22
22
|
* reads matches the mistake it actually made. When neither mode explains the
|
|
23
23
|
* string it stays silent, because speculative advice to a model that is
|
|
24
|
-
* already misdiagnosing is worse than no advice at all.
|
|
24
|
+
* already misdiagnosing is worse than no advice at all. It also measures a
|
|
25
|
+
* string that broke a maximum length, because the Zod message gives the
|
|
26
|
+
* bound and never the length that arrived.
|
|
25
27
|
* - `repairToolInput` — an **issue-guided** repair: only a path where the
|
|
26
28
|
* schema wanted an `object` or `array` and found a string is even considered,
|
|
27
29
|
* so a tool that legitimately declares a `z.string()` field is never touched.
|
|
@@ -55,9 +57,12 @@ const TRAILING_CALL_TAGS = /<\/parameter>\s*(?:<\/invoke>\s*)?$/;
|
|
|
55
57
|
* said. `input` is the raw value that failed validation — the same value handed
|
|
56
58
|
* to `safeParse` — because the issue alone does not carry the offending value.
|
|
57
59
|
*
|
|
58
|
-
* Fires
|
|
59
|
-
*
|
|
60
|
-
*
|
|
60
|
+
* Fires for a `too_big` issue on a string, which yields the length that arrived
|
|
61
|
+
* and the count to remove.
|
|
62
|
+
*
|
|
63
|
+
* Fires otherwise only for an `invalid_type` issue wanting an `object` or an
|
|
64
|
+
* `array` whose path resolves to a string, and only when that string
|
|
65
|
+
* demonstrably *is* the expected value:
|
|
61
66
|
*
|
|
62
67
|
* 1. it parses in full as JSON of the expected type → double encoding, and
|
|
63
68
|
* 2. it parses only once a wrapper artifact is stripped → the value is intact
|
|
@@ -70,6 +75,9 @@ const TRAILING_CALL_TAGS = /<\/parameter>\s*(?:<\/invoke>\s*)?$/;
|
|
|
70
75
|
* `undefined` rather than throwing.
|
|
71
76
|
*/
|
|
72
77
|
export function hintForZodIssue(issue, input) {
|
|
78
|
+
const overLength = hintForOverLongString(issue, input);
|
|
79
|
+
if (overLength !== undefined)
|
|
80
|
+
return overLength;
|
|
73
81
|
const expected = repairableExpectation(issue);
|
|
74
82
|
if (expected === undefined)
|
|
75
83
|
return undefined;
|
|
@@ -123,6 +131,29 @@ export function repairToolInput(input, error) {
|
|
|
123
131
|
return undefined;
|
|
124
132
|
}
|
|
125
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* The measurement of a string that broke its maximum length, or `undefined` for
|
|
136
|
+
* any other issue.
|
|
137
|
+
*
|
|
138
|
+
* A Zod `too_big` message names the bound and stops there. A model reasons in
|
|
139
|
+
* tokens, thus it cannot count the characters that it sent, and it cuts by
|
|
140
|
+
* guess. Each guess costs one rejected call. The hint gives the two numbers
|
|
141
|
+
* that end the guess: the length that arrived, and the count to remove.
|
|
142
|
+
*/
|
|
143
|
+
function hintForOverLongString(issue, input) {
|
|
144
|
+
if (issue.code !== "too_big" || issue.origin !== "string")
|
|
145
|
+
return undefined;
|
|
146
|
+
const value = resolvePath(input, issue.path);
|
|
147
|
+
if (typeof value !== "string")
|
|
148
|
+
return undefined;
|
|
149
|
+
const maximum = Number(issue.maximum);
|
|
150
|
+
if (!Number.isFinite(maximum))
|
|
151
|
+
return undefined;
|
|
152
|
+
const excess = value.length - maximum;
|
|
153
|
+
if (excess <= 0)
|
|
154
|
+
return undefined;
|
|
155
|
+
return `This argument arrived with ${value.length} characters. Remove at least ${excess} characters, and send it again.`;
|
|
156
|
+
}
|
|
126
157
|
/** The `object`/`array` expectation of an `invalid_type` issue, if it has one. */
|
|
127
158
|
function repairableExpectation(issue) {
|
|
128
159
|
if (issue.code !== "invalid_type")
|
|
@@ -28,6 +28,8 @@ import { type SandboxStepDeps, type SandboxStepInput, type SandboxStepResult } f
|
|
|
28
28
|
import { type ExecuteTargetAssessmentDeps, type ExecuteTargetAssessmentInput, type ExecuteTargetAssessmentResult } from "../workflows/execute-target-assessment.js";
|
|
29
29
|
import { type DataProfileDeps, type DataProfileWorkflowInput } from "../tasks/data-profile.js";
|
|
30
30
|
import { type ExtractValuesResult, type ExtractValuesWorkflowInput } from "../tasks/extract-values.js";
|
|
31
|
+
import type { DeriveTableExecInput } from "../tools/report-session/derive-table.js";
|
|
32
|
+
import type { ExecResult } from "../sandbox/types.js";
|
|
31
33
|
import { type CitationResolverConfig } from "../citations/resolve.js";
|
|
32
34
|
import type { CitationResolver } from "../citations/types.js";
|
|
33
35
|
import type { AuthContext } from "../auth/types.js";
|
|
@@ -61,6 +63,7 @@ export interface RegisteredWorkflows {
|
|
|
61
63
|
readonly executeTargetAssessment: (input: ExecuteTargetAssessmentInput) => Promise<ExecuteTargetAssessmentResult>;
|
|
62
64
|
readonly dataProfile: (input: DataProfileWorkflowInput) => Promise<void>;
|
|
63
65
|
readonly extractValues: (input: ExtractValuesWorkflowInput) => Promise<ExtractValuesResult>;
|
|
66
|
+
readonly deriveTableExec: (input: DeriveTableExecInput) => Promise<ExecResult>;
|
|
64
67
|
}
|
|
65
68
|
/**
|
|
66
69
|
* Conversation-agent deps minus the workflow callable, the resource policy, the
|
package/dist/runtime/assemble.js
CHANGED
|
@@ -26,6 +26,7 @@ import { registerSandboxStep } from "../workflows/sandbox-step.js";
|
|
|
26
26
|
import { registerExecuteTargetAssessment, } from "../workflows/execute-target-assessment.js";
|
|
27
27
|
import { registerDataProfileWorkflow } from "../tasks/data-profile.js";
|
|
28
28
|
import { bindExtractionTrigger, createExtractionArm, registerExtractValuesWorkflow, } from "../tasks/extract-values.js";
|
|
29
|
+
import { registerDeriveTableExecWorkflow, triggerDeriveTableExec } from "../tasks/derive-table-exec.js";
|
|
29
30
|
import { createCitationResolver } from "../citations/resolve.js";
|
|
30
31
|
import { createThreadStore } from "../memory/thread-store.js";
|
|
31
32
|
import { createArtifactReadStore, createProductionResolver } from "../report-model/production-resolver.js";
|
|
@@ -95,6 +96,12 @@ export function assembleCoreRuntime(deps) {
|
|
|
95
96
|
runAuthorizer: wf.dataProfile.runAuthorizer,
|
|
96
97
|
...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
|
|
97
98
|
});
|
|
99
|
+
// A session derivation runs its container here and not in the turn. The await of an exec is a
|
|
100
|
+
// workflow-body call under the callback transport, thus the tool starts this workflow and awaits it.
|
|
101
|
+
const deriveTableExec = registerDeriveTableExecWorkflow({
|
|
102
|
+
sandboxClient: wf.dataProfile.sandboxClient,
|
|
103
|
+
...(wf.dataProfile.logger ? { logger: wf.dataProfile.logger } : {}),
|
|
104
|
+
});
|
|
98
105
|
// The session runtime binds the per-session state to the thread behind the
|
|
99
106
|
// tool boundary, thus one instance serves every report thread. It comes before
|
|
100
107
|
// the conversation agent, because `start_report_session` takes its anchor
|
|
@@ -159,7 +166,7 @@ export function assembleCoreRuntime(deps) {
|
|
|
159
166
|
// and the same run authorizer. Thus a session derivation and an extraction run on
|
|
160
167
|
// one substrate, and neither one mints a run of the analysis.
|
|
161
168
|
derivations: reportSession.derivations,
|
|
162
|
-
|
|
169
|
+
runDerivation: (input) => triggerDeriveTableExec(deriveTableExec, input),
|
|
163
170
|
runAuthorizer: conversation.runAuthorizer,
|
|
164
171
|
makeResolver: makeReportResolver,
|
|
165
172
|
...(eyes ? { eyes } : {}),
|
|
@@ -188,6 +195,7 @@ export function assembleCoreRuntime(deps) {
|
|
|
188
195
|
executeTargetAssessment,
|
|
189
196
|
dataProfile,
|
|
190
197
|
extractValues,
|
|
198
|
+
deriveTableExec,
|
|
191
199
|
},
|
|
192
200
|
citationResolver,
|
|
193
201
|
};
|
package/dist/sandbox/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `SandboxClient` — the
|
|
2
|
+
* `SandboxClient` — the seam every sandbox-backed module in
|
|
3
3
|
* the harness sits on. Implementations are backend-selected
|
|
4
4
|
* (`docker`/`k8s`) by `createSandboxClient()` in `create-sandbox.ts` and
|
|
5
5
|
* injected at the composition root as a construction-time dependency
|
|
@@ -17,20 +17,16 @@
|
|
|
17
17
|
* Many execs may fire against the same machine.
|
|
18
18
|
* - The **exec** lifetime is one `submitExec → awaitExec`.
|
|
19
19
|
*
|
|
20
|
-
* `awaitExec` runs
|
|
21
|
-
*
|
|
20
|
+
* **Every caller of `awaitExec` runs inside a DBOS workflow body.** `DBOS.recv`
|
|
21
|
+
* and `DBOS.writeStream` are body-only, thus the callback transport cannot
|
|
22
|
+
* settle anywhere else, and the poll transport reaches for `DBOS.runStep` and
|
|
23
|
+
* `DBOS.sleepms` defaults. A caller that starts from a live chat turn registers
|
|
24
|
+
* its own workflow and starts it: `tasks/extract-values.ts` and
|
|
25
|
+
* `tasks/derive-table-exec.ts` are the two worked examples. No type carries this
|
|
26
|
+
* rule yet, thus a new caller must read this paragraph.
|
|
22
27
|
*/
|
|
23
|
-
import type { CreateSandboxMeta, ExecEmit, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef,
|
|
28
|
+
import type { CreateSandboxMeta, ExecEmit, ExecResult, ManagedSandbox, SandboxIdentity, SandboxLiveness, SandboxRef, SubmitExecBody } from "./types.js";
|
|
24
29
|
export interface SandboxClient {
|
|
25
|
-
/**
|
|
26
|
-
* The result transport this client awaits under, fixed at construction. A caller that runs outside a
|
|
27
|
-
* workflow body reads it: `callback` awaits through `DBOS.recv`, which is body-only, thus such a caller
|
|
28
|
-
* must refuse before it starts a container it can never await.
|
|
29
|
-
*
|
|
30
|
-
* Optional, because a hand-written realization states nothing here. Absent means unknown, and a caller
|
|
31
|
-
* treats it as the poll default rather than refusing every composition that omits it.
|
|
32
|
-
*/
|
|
33
|
-
readonly transport?: SandboxTransport;
|
|
34
30
|
/**
|
|
35
31
|
* DBOS step (`sandbox.create`) — the spawn half of the two-step create
|
|
36
32
|
* (see the harness-sandbox-exec spec). Launches the sandbox-base container/Job under the
|
package/dist/sandbox/client.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `SandboxClient` — the
|
|
2
|
+
* `SandboxClient` — the seam every sandbox-backed module in
|
|
3
3
|
* the harness sits on. Implementations are backend-selected
|
|
4
4
|
* (`docker`/`k8s`) by `createSandboxClient()` in `create-sandbox.ts` and
|
|
5
5
|
* injected at the composition root as a construction-time dependency
|
|
@@ -17,7 +17,12 @@
|
|
|
17
17
|
* Many execs may fire against the same machine.
|
|
18
18
|
* - The **exec** lifetime is one `submitExec → awaitExec`.
|
|
19
19
|
*
|
|
20
|
-
* `awaitExec` runs
|
|
21
|
-
*
|
|
20
|
+
* **Every caller of `awaitExec` runs inside a DBOS workflow body.** `DBOS.recv`
|
|
21
|
+
* and `DBOS.writeStream` are body-only, thus the callback transport cannot
|
|
22
|
+
* settle anywhere else, and the poll transport reaches for `DBOS.runStep` and
|
|
23
|
+
* `DBOS.sleepms` defaults. A caller that starts from a live chat turn registers
|
|
24
|
+
* its own workflow and starts it: `tasks/extract-values.ts` and
|
|
25
|
+
* `tasks/derive-table-exec.ts` are the two worked examples. No type carries this
|
|
26
|
+
* rule yet, thus a new caller must read this paragraph.
|
|
22
27
|
*/
|
|
23
28
|
export {};
|
|
@@ -127,7 +127,6 @@ export function createSandboxClient(config) {
|
|
|
127
127
|
// poll loop's escalation probe are the same backend inspect.
|
|
128
128
|
const isAlive = async (ref) => unwrapOrThrow(await ops.isAlive(ref));
|
|
129
129
|
return {
|
|
130
|
-
transport,
|
|
131
130
|
createSandbox: async (meta, identity) => {
|
|
132
131
|
// The config states an engine fact (binds preserve host ownership);
|
|
133
132
|
// which remediation that fact demands — today, a world-writable step
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The container of one report-session derivation, as a registered DBOS workflow.
|
|
3
|
+
*
|
|
4
|
+
* The `derive_table` tool runs inside one live chat turn, and a chat turn is not a workflow body. An await
|
|
5
|
+
* of an exec under the callback transport calls `DBOS.recv`, which a workflow body alone can call. Thus the
|
|
6
|
+
* container lives here, behind a registered workflow, and the tool starts it and awaits the handle.
|
|
7
|
+
*
|
|
8
|
+
* The shape is the shape of `tasks/extract-values.ts`: a body that a test drives directly, a registration
|
|
9
|
+
* that closes over the construction-time deps, and a trigger that starts the workflow and gives the result
|
|
10
|
+
* back. The tool holds the trigger as a plain function, thus no module under `tools/` imports DBOS.
|
|
11
|
+
*
|
|
12
|
+
* The body owns the whole machine lifetime. It creates the container, submits one exec, awaits the terminal
|
|
13
|
+
* result, and tears the container down on both paths. A teardown fault reaches the log alone, because the
|
|
14
|
+
* work is done by then.
|
|
15
|
+
*/
|
|
16
|
+
import type { Logger } from "../lib/logger.js";
|
|
17
|
+
import type { SandboxClient } from "../sandbox/client.js";
|
|
18
|
+
import type { ExecResult } from "../sandbox/types.js";
|
|
19
|
+
import { type DeriveTableExecInput } from "../tools/report-session/derive-table.js";
|
|
20
|
+
/** The construction-time deps of the body. The registration closes over them, thus the trigger holds none. */
|
|
21
|
+
export interface DeriveTableExecDeps {
|
|
22
|
+
readonly sandboxClient: SandboxClient;
|
|
23
|
+
/**
|
|
24
|
+
* The checkpointed clock of the deadline. It defaults to `DBOS.now()`, which a replay reads again from
|
|
25
|
+
* the checkpoint. A test injects a fixed clock, thus it drives this body with no launched runtime.
|
|
26
|
+
*/
|
|
27
|
+
readonly now?: () => Promise<number>;
|
|
28
|
+
readonly logger?: Logger;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The body, exported so a test drives it without a registered workflow.
|
|
32
|
+
*
|
|
33
|
+
* A fault of any seam call throws. The tool reads a rejection as one short detail, thus the caller needs no
|
|
34
|
+
* result type here.
|
|
35
|
+
*/
|
|
36
|
+
export declare function runDeriveTableExecBody(input: DeriveTableExecInput, deps: DeriveTableExecDeps): Promise<ExecResult>;
|
|
37
|
+
/**
|
|
38
|
+
* Register the derivation workflow with DBOS. It gives back the registered callable, thus a trigger
|
|
39
|
+
* dispatches with `DBOS.startWorkflow`.
|
|
40
|
+
*/
|
|
41
|
+
export declare function registerDeriveTableExecWorkflow(deps: DeriveTableExecDeps): (input: DeriveTableExecInput) => Promise<ExecResult>;
|
|
42
|
+
/** The per-attempt workflow id. The execution id is unique already, and the nonce keeps a retry distinct. */
|
|
43
|
+
export declare function deriveTableExecWorkflowId(executionId: string, nonce: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* The async edge. It starts the registered workflow and it awaits the terminal result.
|
|
46
|
+
*
|
|
47
|
+
* The tool authorizes the derivation before this call and it revokes after, thus this edge holds no
|
|
48
|
+
* lifecycle of its own. A workflow fault rejects the returned promise.
|
|
49
|
+
*/
|
|
50
|
+
export declare function triggerDeriveTableExec(workflow: (input: DeriveTableExecInput) => Promise<ExecResult>, input: DeriveTableExecInput): Promise<ExecResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The container of one report-session derivation, as a registered DBOS workflow.
|
|
3
|
+
*
|
|
4
|
+
* The `derive_table` tool runs inside one live chat turn, and a chat turn is not a workflow body. An await
|
|
5
|
+
* of an exec under the callback transport calls `DBOS.recv`, which a workflow body alone can call. Thus the
|
|
6
|
+
* container lives here, behind a registered workflow, and the tool starts it and awaits the handle.
|
|
7
|
+
*
|
|
8
|
+
* The shape is the shape of `tasks/extract-values.ts`: a body that a test drives directly, a registration
|
|
9
|
+
* that closes over the construction-time deps, and a trigger that starts the workflow and gives the result
|
|
10
|
+
* back. The tool holds the trigger as a plain function, thus no module under `tools/` imports DBOS.
|
|
11
|
+
*
|
|
12
|
+
* The body owns the whole machine lifetime. It creates the container, submits one exec, awaits the terminal
|
|
13
|
+
* result, and tears the container down on both paths. A teardown fault reaches the log alone, because the
|
|
14
|
+
* work is done by then.
|
|
15
|
+
*/
|
|
16
|
+
import { DBOS } from "@dbos-inc/dbos-sdk";
|
|
17
|
+
import { randomUUID } from "node:crypto";
|
|
18
|
+
import { createNoopLogger } from "../lib/console-logger.js";
|
|
19
|
+
import { mintSandboxIdentity } from "../sandbox/identity.js";
|
|
20
|
+
import { buildDerivationExec, DERIVATION_DEADLINE_MS, DERIVATION_RESOURCES, DERIVE_RUN_LITERAL, DERIVE_STEP_LITERAL, } from "../tools/report-session/derive-table.js";
|
|
21
|
+
/** The awaitExec callback. A derivation reports no live activity, thus the callback drops each event. */
|
|
22
|
+
const noopEmit = () => { };
|
|
23
|
+
/**
|
|
24
|
+
* The body, exported so a test drives it without a registered workflow.
|
|
25
|
+
*
|
|
26
|
+
* A fault of any seam call throws. The tool reads a rejection as one short detail, thus the caller needs no
|
|
27
|
+
* result type here.
|
|
28
|
+
*/
|
|
29
|
+
export async function runDeriveTableExecBody(input, deps) {
|
|
30
|
+
const logger = (deps.logger ?? createNoopLogger()).named("derive-table-exec").with({ analysisId: input.analysisId });
|
|
31
|
+
const workflowId = DBOS.workflowID ?? `${DERIVE_RUN_LITERAL}:${input.executionId}`;
|
|
32
|
+
const sandbox = await deps.sandboxClient.createSandbox({
|
|
33
|
+
runId: DERIVE_RUN_LITERAL,
|
|
34
|
+
stepId: DERIVE_STEP_LITERAL,
|
|
35
|
+
analysisId: input.analysisId,
|
|
36
|
+
childWorkflowId: workflowId,
|
|
37
|
+
resources: DERIVATION_RESOURCES,
|
|
38
|
+
writableTail: input.writableTail,
|
|
39
|
+
}, mintSandboxIdentity(DERIVE_RUN_LITERAL));
|
|
40
|
+
try {
|
|
41
|
+
// A checkpointed clock, not `Date.now()`: the await gates on this absolute deadline, and a
|
|
42
|
+
// wall-clock deadline that grew on replay would shift which loop iteration crosses it.
|
|
43
|
+
const deadline = (await (deps.now ?? (() => DBOS.now()))()) + DERIVATION_DEADLINE_MS;
|
|
44
|
+
await deps.sandboxClient.submitExec(sandbox, buildDerivationExec({
|
|
45
|
+
script: input.script,
|
|
46
|
+
execId: input.executionId,
|
|
47
|
+
workingDir: input.workingDir,
|
|
48
|
+
inputs: input.inputs,
|
|
49
|
+
output: input.output,
|
|
50
|
+
}));
|
|
51
|
+
return await deps.sandboxClient.awaitExec(sandbox, input.executionId, noopEmit, deadline);
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
try {
|
|
55
|
+
await deps.sandboxClient.teardown(sandbox);
|
|
56
|
+
}
|
|
57
|
+
catch (cause) {
|
|
58
|
+
logger.warn("the derivation sandbox did not tear down", logger.errorFields(cause));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Register the derivation workflow with DBOS. It gives back the registered callable, thus a trigger
|
|
64
|
+
* dispatches with `DBOS.startWorkflow`.
|
|
65
|
+
*/
|
|
66
|
+
export function registerDeriveTableExecWorkflow(deps) {
|
|
67
|
+
return DBOS.registerWorkflow((input) => runDeriveTableExecBody(input, deps), { name: "derive-table-exec" });
|
|
68
|
+
}
|
|
69
|
+
/** The per-attempt workflow id. The execution id is unique already, and the nonce keeps a retry distinct. */
|
|
70
|
+
export function deriveTableExecWorkflowId(executionId, nonce) {
|
|
71
|
+
return `${DERIVE_RUN_LITERAL}:${executionId}:${nonce}`;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* The async edge. It starts the registered workflow and it awaits the terminal result.
|
|
75
|
+
*
|
|
76
|
+
* The tool authorizes the derivation before this call and it revokes after, thus this edge holds no
|
|
77
|
+
* lifecycle of its own. A workflow fault rejects the returned promise.
|
|
78
|
+
*/
|
|
79
|
+
export async function triggerDeriveTableExec(workflow, input) {
|
|
80
|
+
const handle = (await DBOS.startWorkflow(workflow, {
|
|
81
|
+
workflowID: deriveTableExecWorkflowId(input.executionId, randomUUID()),
|
|
82
|
+
})(input));
|
|
83
|
+
return handle.getResult();
|
|
84
|
+
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* A session derivation is not an analysis run. It mints no run id, it registers no artifact, and it writes
|
|
9
9
|
* under the session directory alone. The rails are the rails of the value tier (`tasks/extract-values.ts`):
|
|
10
|
-
* the
|
|
10
|
+
* the injected exec runner, the identity mint, and the authorizer on every terminal path. The container is
|
|
11
11
|
* ephemeral, and it goes away with the work.
|
|
12
12
|
*
|
|
13
13
|
* The container mounts the analysis tree read-only, and one write mount covers the `derived/` directory of
|
|
@@ -29,15 +29,14 @@
|
|
|
29
29
|
* condition one time, up front, the same discipline as the eyes. A per-attempt failure would instead read as
|
|
30
30
|
* a transient fault, and it would invite a repeat of a call that can never pass.
|
|
31
31
|
*
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* tool refuses up front under it rather than start a container that nothing can await.
|
|
32
|
+
* The container runs behind a seam, and never in the turn. An await of an exec is a workflow-body call
|
|
33
|
+
* under the callback transport, and a report turn is not a body. Thus the tool takes an injected runner,
|
|
34
|
+
* and a registered workflow owns the container. The tool holds no sandbox client, and it imports no DBOS.
|
|
36
35
|
*/
|
|
37
36
|
import { z } from "zod";
|
|
38
37
|
import type { RunAuthorizer } from "../../execution/run-authorizer.js";
|
|
39
38
|
import { type Logger } from "../../lib/logger.js";
|
|
40
|
-
import type {
|
|
39
|
+
import type { ResourceSpec } from "../../config/resource-limits.js";
|
|
41
40
|
import type { ExecResult, SubmitExecBody } from "../../sandbox/types.js";
|
|
42
41
|
import type { DerivationSource, ReportSessionStateStore } from "../../state/report-session-state.js";
|
|
43
42
|
import { type ResolveWorkspaceRoot } from "../../workspace/paths.js";
|
|
@@ -113,10 +112,20 @@ export interface DeriveTableToolDeps {
|
|
|
113
112
|
readonly gateway: ReportSessionStateGateway;
|
|
114
113
|
readonly resolveWorkspaceRoot: ResolveWorkspaceRoot;
|
|
115
114
|
readonly derivations: Pick<ReportSessionStateStore, "appendDerivation">;
|
|
116
|
-
readonly
|
|
115
|
+
readonly runDerivation?: DeriveTableRunner;
|
|
117
116
|
readonly runAuthorizer?: RunAuthorizer;
|
|
118
117
|
readonly logger?: Logger;
|
|
119
118
|
}
|
|
119
|
+
/** The synthetic run id and step id of one derivation. Both are constants, and neither names a run row. */
|
|
120
|
+
export declare const DERIVE_RUN_LITERAL: "derive-table";
|
|
121
|
+
export declare const DERIVE_STEP_LITERAL: "derive";
|
|
122
|
+
/** The exec budget of one derivation. It matches the budget of the value tier. */
|
|
123
|
+
export declare const DERIVATION_DEADLINE_MS = 300000;
|
|
124
|
+
/**
|
|
125
|
+
* The container size of one derivation. A join or a pivot loads each declared input into pandas at one
|
|
126
|
+
* time, thus the container needs the headroom of the value tier.
|
|
127
|
+
*/
|
|
128
|
+
export declare const DERIVATION_RESOURCES: ResourceSpec;
|
|
120
129
|
/** The environment variable that carries the declared inputs into the container. */
|
|
121
130
|
export declare const DERIVE_INPUT_ENV = "DERIVE_INPUTS";
|
|
122
131
|
/** The environment variable that carries the container path of the output file into the container. */
|
|
@@ -152,6 +161,29 @@ export declare function buildDerivationExec(args: {
|
|
|
152
161
|
* The function is pure, thus a test asserts the mapping without a sandbox.
|
|
153
162
|
*/
|
|
154
163
|
export declare function describeExecFailure(result: ExecResult): string | undefined;
|
|
164
|
+
/**
|
|
165
|
+
* The exec of one derivation, as a registered workflow reads it.
|
|
166
|
+
*
|
|
167
|
+
* Each field is plain data, thus the value crosses the workflow boundary and it survives a replay. The
|
|
168
|
+
* paths are container paths already: the host maps them one time, before the start.
|
|
169
|
+
*/
|
|
170
|
+
export interface DeriveTableExecInput {
|
|
171
|
+
readonly analysisId: string;
|
|
172
|
+
readonly executionId: string;
|
|
173
|
+
readonly script: string;
|
|
174
|
+
readonly writableTail: string;
|
|
175
|
+
readonly workingDir: string;
|
|
176
|
+
readonly inputs: readonly DerivationInputMount[];
|
|
177
|
+
readonly output: string;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Run one derivation exec and give the terminal result back.
|
|
181
|
+
*
|
|
182
|
+
* The composition realizes it over a registered workflow, thus the container lives inside a workflow body
|
|
183
|
+
* and the await is legal under each transport. A fault of the sandbox rejects the promise, and the tool
|
|
184
|
+
* turns that rejection into one short detail.
|
|
185
|
+
*/
|
|
186
|
+
export type DeriveTableRunner = (input: DeriveTableExecInput) => Promise<ExecResult>;
|
|
155
187
|
/**
|
|
156
188
|
* Make the derivation tool over the session-state gateway, the derivation ledger, and the sandbox rails.
|
|
157
189
|
*
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*
|
|
8
8
|
* A session derivation is not an analysis run. It mints no run id, it registers no artifact, and it writes
|
|
9
9
|
* under the session directory alone. The rails are the rails of the value tier (`tasks/extract-values.ts`):
|
|
10
|
-
* the
|
|
10
|
+
* the injected exec runner, the identity mint, and the authorizer on every terminal path. The container is
|
|
11
11
|
* ephemeral, and it goes away with the work.
|
|
12
12
|
*
|
|
13
13
|
* The container mounts the analysis tree read-only, and one write mount covers the `derived/` directory of
|
|
@@ -29,12 +29,11 @@
|
|
|
29
29
|
* condition one time, up front, the same discipline as the eyes. A per-attempt failure would instead read as
|
|
30
30
|
* a transient fault, and it would invite a repeat of a call that can never pass.
|
|
31
31
|
*
|
|
32
|
-
* The
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* tool refuses up front under it rather than start a container that nothing can await.
|
|
32
|
+
* The container runs behind a seam, and never in the turn. An await of an exec is a workflow-body call
|
|
33
|
+
* under the callback transport, and a report turn is not a body. Thus the tool takes an injected runner,
|
|
34
|
+
* and a registered workflow owns the container. The tool holds no sandbox client, and it imports no DBOS.
|
|
36
35
|
*/
|
|
37
|
-
import {
|
|
36
|
+
import { ok } from "neverthrow";
|
|
38
37
|
import { unlink } from "node:fs/promises";
|
|
39
38
|
import { join } from "node:path";
|
|
40
39
|
import { z } from "zod";
|
|
@@ -43,7 +42,6 @@ import { classifyWithinRoot, computeSha256, computeSha256File } from "../../lib/
|
|
|
43
42
|
import { defaultErrorFields } from "../../lib/logger.js";
|
|
44
43
|
import { snapshotEntry } from "../../report-model/reference-resolver.js";
|
|
45
44
|
import { generateExecutionId } from "../../sandbox/execution-id.js";
|
|
46
|
-
import { mintSandboxIdentity } from "../../sandbox/identity.js";
|
|
47
45
|
import { isSafeId, reportSessionDerivedDir, toSandboxPath } from "../../workspace/paths.js";
|
|
48
46
|
import { defineTool } from "../define-tool.js";
|
|
49
47
|
import { openReportThread } from "../report-authoring/authoring-tools.js";
|
|
@@ -55,17 +53,17 @@ const deriveTableInput = z.object({
|
|
|
55
53
|
output: z.string().min(1),
|
|
56
54
|
});
|
|
57
55
|
/** The synthetic run id and step id of one derivation. Both are constants, and neither names a run row. */
|
|
58
|
-
const DERIVE_RUN_LITERAL = "derive-table";
|
|
59
|
-
const DERIVE_STEP_LITERAL = "derive";
|
|
56
|
+
export const DERIVE_RUN_LITERAL = "derive-table";
|
|
57
|
+
export const DERIVE_STEP_LITERAL = "derive";
|
|
60
58
|
/** The provenance agent id. No agent loop runs in the container, thus this names the derivation pass. */
|
|
61
59
|
const DERIVE_AGENT_ID = "table-deriver";
|
|
62
60
|
/** The exec budget of one derivation. It matches the budget of the value tier. */
|
|
63
|
-
const DERIVATION_DEADLINE_MS = 300_000;
|
|
61
|
+
export const DERIVATION_DEADLINE_MS = 300_000;
|
|
64
62
|
/**
|
|
65
63
|
* The container size of one derivation. A join or a pivot loads each declared input into pandas at one
|
|
66
64
|
* time, thus the container needs the headroom of the value tier.
|
|
67
65
|
*/
|
|
68
|
-
const DERIVATION_RESOURCES = { cpu: 2, memoryGb: 8 };
|
|
66
|
+
export const DERIVATION_RESOURCES = { cpu: 2, memoryGb: 8 };
|
|
69
67
|
/** The cap of the script, in bytes. A reshaping script sits far under it, and the command line carries it. */
|
|
70
68
|
const SCRIPT_CAP_BYTES = 64 * 1024;
|
|
71
69
|
/** The cap of the declared inputs. One derivation reads the evidence of a few artifacts, and never a tree. */
|
|
@@ -76,12 +74,8 @@ export const DERIVE_INPUT_ENV = "DERIVE_INPUTS";
|
|
|
76
74
|
export const DERIVE_OUTPUT_ENV = "DERIVE_OUTPUT";
|
|
77
75
|
/** The cap of the failure detail that the tool copies out of the standard error of the script. */
|
|
78
76
|
const STDERR_TAIL_CAP = 2_000;
|
|
79
|
-
/** The awaitExec callback. A derivation reports no live activity, thus the callback drops each event. */
|
|
80
|
-
const noopEmit = () => { };
|
|
81
77
|
/** The line that the agent reads when the composition binds no sandbox rails. */
|
|
82
78
|
const NO_SANDBOX_DETAIL = "the composition gives no sandbox, thus this session cannot derive a table";
|
|
83
|
-
/** The line that the agent reads when the sandbox client awaits under a transport that a turn cannot drive. */
|
|
84
|
-
const CALLBACK_TRANSPORT_DETAIL = "the sandbox of this composition reports through callbacks, which a report turn cannot await, thus it derives no table";
|
|
85
79
|
/**
|
|
86
80
|
* Build the sandbox exec of one derivation. The command runs the script of the agent through `python3 -c`.
|
|
87
81
|
*
|
|
@@ -126,58 +120,6 @@ export function describeExecFailure(result) {
|
|
|
126
120
|
}
|
|
127
121
|
return undefined;
|
|
128
122
|
}
|
|
129
|
-
/**
|
|
130
|
-
* Run one derivation in an ephemeral container, and give the exec result back.
|
|
131
|
-
*
|
|
132
|
-
* The container mounts the analysis tree read-only, and the declared write tail is its one writable mount.
|
|
133
|
-
* Thus the script reads the evidence and it writes into the `derived/` directory of the session and nowhere
|
|
134
|
-
* else. The teardown runs on both paths, thus no machine outlives the call. A teardown fault reaches the log
|
|
135
|
-
* alone, because the work is done.
|
|
136
|
-
*
|
|
137
|
-
* Each seam call is guarded, thus a fault of the sandbox becomes a short detail and the tool keeps its
|
|
138
|
-
* no-throw contract.
|
|
139
|
-
*/
|
|
140
|
-
async function runDerivationExec(args) {
|
|
141
|
-
let sandbox;
|
|
142
|
-
try {
|
|
143
|
-
sandbox = await args.client.createSandbox({
|
|
144
|
-
runId: DERIVE_RUN_LITERAL,
|
|
145
|
-
stepId: DERIVE_STEP_LITERAL,
|
|
146
|
-
analysisId: args.analysisId,
|
|
147
|
-
childWorkflowId: `${DERIVE_RUN_LITERAL}:${args.executionId}`,
|
|
148
|
-
resources: DERIVATION_RESOURCES,
|
|
149
|
-
writableTail: args.writableTail,
|
|
150
|
-
}, mintSandboxIdentity(DERIVE_RUN_LITERAL));
|
|
151
|
-
}
|
|
152
|
-
catch (cause) {
|
|
153
|
-
args.logger.error("the derivation sandbox did not start", args.logger.errorFields(cause));
|
|
154
|
-
return err("the derivation sandbox did not start");
|
|
155
|
-
}
|
|
156
|
-
try {
|
|
157
|
-
// The tool runs inside one live turn and it never replays, thus the wall clock is the deadline.
|
|
158
|
-
const deadline = Date.now() + DERIVATION_DEADLINE_MS;
|
|
159
|
-
await args.client.submitExec(sandbox, buildDerivationExec({
|
|
160
|
-
script: args.script,
|
|
161
|
-
execId: args.executionId,
|
|
162
|
-
workingDir: args.workingDir,
|
|
163
|
-
inputs: args.inputs,
|
|
164
|
-
output: args.output,
|
|
165
|
-
}));
|
|
166
|
-
return ok(await args.client.awaitExec(sandbox, args.executionId, noopEmit, deadline));
|
|
167
|
-
}
|
|
168
|
-
catch (cause) {
|
|
169
|
-
args.logger.error("the derivation exec did not complete", args.logger.errorFields(cause));
|
|
170
|
-
return err("the derivation exec did not complete");
|
|
171
|
-
}
|
|
172
|
-
finally {
|
|
173
|
-
try {
|
|
174
|
-
await args.client.teardown(sandbox);
|
|
175
|
-
}
|
|
176
|
-
catch (cause) {
|
|
177
|
-
args.logger.warn("the derivation sandbox did not tear down", args.logger.errorFields(cause));
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
123
|
/**
|
|
182
124
|
* Make the derivation tool over the session-state gateway, the derivation ledger, and the sandbox rails.
|
|
183
125
|
*
|
|
@@ -186,7 +128,7 @@ async function runDerivationExec(args) {
|
|
|
186
128
|
*/
|
|
187
129
|
export function createDeriveTableTool(deps) {
|
|
188
130
|
const logger = (deps.logger ?? createNoopLogger()).named("derive-table");
|
|
189
|
-
const {
|
|
131
|
+
const { runDerivation, runAuthorizer } = deps;
|
|
190
132
|
return defineTool({
|
|
191
133
|
id: "derive_table",
|
|
192
134
|
description: "Derive one table from the pinned evidence with a Python script, and pin the result to this session. " +
|
|
@@ -209,16 +151,10 @@ export function createDeriveTableTool(deps) {
|
|
|
209
151
|
execute: async (input, ctx) => {
|
|
210
152
|
// The check runs before every read, thus one clear signal replaces a failure for each attempt.
|
|
211
153
|
// It also narrows the two rails below, thus the exec needs no assertion.
|
|
212
|
-
if (
|
|
154
|
+
if (runDerivation === undefined || runAuthorizer === undefined) {
|
|
213
155
|
logger.warn("the composition gives no sandbox, thus no derivation can run");
|
|
214
156
|
return ok({ outcome: "unavailable", detail: NO_SANDBOX_DETAIL });
|
|
215
157
|
}
|
|
216
|
-
// The await of the callback transport is a workflow-body call, and a report turn is not one. An
|
|
217
|
-
// absent field states nothing, thus it reads as the poll default and it refuses nothing.
|
|
218
|
-
if (sandboxClient.transport === "callback") {
|
|
219
|
-
logger.warn("the sandbox client awaits through callbacks, thus no derivation can run in a turn");
|
|
220
|
-
return ok({ outcome: "unavailable", detail: CALLBACK_TRANSPORT_DETAIL });
|
|
221
|
-
}
|
|
222
158
|
const opened = await openReportThread(deps.gateway, ctx.session.scope);
|
|
223
159
|
if (opened.isErr()) {
|
|
224
160
|
return ok({ outcome: "refused", refusal: opened.error });
|
|
@@ -304,7 +240,7 @@ export function createDeriveTableTool(deps) {
|
|
|
304
240
|
let result;
|
|
305
241
|
try {
|
|
306
242
|
result = await derive({
|
|
307
|
-
|
|
243
|
+
runDerivation,
|
|
308
244
|
derivations: deps.derivations,
|
|
309
245
|
root,
|
|
310
246
|
threadId,
|
|
@@ -363,8 +299,9 @@ async function derive(args) {
|
|
|
363
299
|
return { outcome: "exec-failed", detail: `${args.outputName} already holds a file that did not clear, thus this derivation takes a new name` };
|
|
364
300
|
}
|
|
365
301
|
}
|
|
366
|
-
|
|
367
|
-
|
|
302
|
+
// The path mapper refuses a stored path that escapes the root, with a throw. It runs before the try,
|
|
303
|
+
// thus that refusal stays a throw and it never reads as a fault of the container.
|
|
304
|
+
const execInput = {
|
|
368
305
|
analysisId: args.analysisId,
|
|
369
306
|
executionId,
|
|
370
307
|
script: args.script,
|
|
@@ -372,12 +309,16 @@ async function derive(args) {
|
|
|
372
309
|
workingDir,
|
|
373
310
|
inputs: args.sources.map((source) => ({ path: toSandboxPath(args.root, args.analysisId, join(args.root, source.path)), hash: source.hash })),
|
|
374
311
|
output: toSandboxPath(args.root, args.analysisId, absolute),
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
312
|
+
};
|
|
313
|
+
let executed;
|
|
314
|
+
try {
|
|
315
|
+
executed = await args.runDerivation(execInput);
|
|
316
|
+
}
|
|
317
|
+
catch (cause) {
|
|
318
|
+
args.logger.error("the derivation exec did not complete", { threadId: args.threadId, ...defaultErrorFields(cause) });
|
|
319
|
+
return { outcome: "exec-failed", detail: "the derivation exec did not complete" };
|
|
379
320
|
}
|
|
380
|
-
const failure = describeExecFailure(executed
|
|
321
|
+
const failure = describeExecFailure(executed);
|
|
381
322
|
if (failure !== undefined) {
|
|
382
323
|
return { outcome: "exec-failed", detail: failure };
|
|
383
324
|
}
|
|
@@ -27,16 +27,20 @@ import { defineTool } from "./define-tool.js";
|
|
|
27
27
|
* message row, thus the schema holds no unbounded string out of the store.
|
|
28
28
|
*
|
|
29
29
|
* The description caps the brief at approximately 2000 tokens, and a token is
|
|
30
|
-
* about four characters. Thus the five bounds sum to
|
|
30
|
+
* about four characters. Thus the five bounds sum to 8200 characters, and the
|
|
31
31
|
* widest brief that the schema admits still meets the cap that the agent reads.
|
|
32
32
|
*
|
|
33
33
|
* The share of each field follows its role. `audience` names one reader, thus a
|
|
34
34
|
* phrase is enough. `objective` and `angle` carry a sentence or two. Each
|
|
35
35
|
* optional field carries a list of items, thus it takes the largest share.
|
|
36
|
+
*
|
|
37
|
+
* The bound of `audience` holds 400 characters, not the 200 that the phrase
|
|
38
|
+
* alone needs. A model counts tokens, not characters, thus a tight bound gives
|
|
39
|
+
* a rejected call and a retry.
|
|
36
40
|
*/
|
|
37
41
|
const BRIEF_MAX = {
|
|
38
42
|
objective: 1500,
|
|
39
|
-
audience:
|
|
43
|
+
audience: 400,
|
|
40
44
|
angle: 1500,
|
|
41
45
|
exclusions: 2400,
|
|
42
46
|
openQuestions: 2400,
|
package/package.json
CHANGED