@tangle-network/agent-runtime 0.126.0 → 0.131.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.
- package/README.md +70 -20
- package/dist/{activation-DhWJ3p8N.js → activation-BCzMOTaV.js} +3 -3
- package/dist/{activation-DhWJ3p8N.js.map → activation-BCzMOTaV.js.map} +1 -1
- package/dist/agent.d.ts +2 -3
- package/dist/agent.js +4 -5
- package/dist/agent.js.map +1 -1
- package/dist/{analyst-loop-DvSciOfB.js → analyst-loop-BE8cDs5Q.js} +2 -2
- package/dist/{analyst-loop-DvSciOfB.js.map → analyst-loop-BE8cDs5Q.js.map} +1 -1
- package/dist/analyst-loop.d.ts +1 -1
- package/dist/analyst-loop.js +1 -1
- package/dist/authoring-CvHwo1oW.js +163 -0
- package/dist/authoring-CvHwo1oW.js.map +1 -0
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{candidate-execution-BFpq-Xi6.js → candidate-execution-BNxKr-Bu.js} +5 -5
- package/dist/{candidate-execution-BFpq-Xi6.js.map → candidate-execution-BNxKr-Bu.js.map} +1 -1
- package/dist/{conversation-BpLQZGPH.js → conversation-DNtxaJ1Z.js} +113 -31
- package/dist/conversation-DNtxaJ1Z.js.map +1 -0
- package/dist/conversation.d.ts +2 -2
- package/dist/conversation.js +2 -2
- package/dist/environment-provider-CxvSd1W6.d.ts +86 -0
- package/dist/{environment-provider-DqFS6FSZ.js → environment-provider-Dyg8DtLK.js} +8 -4
- package/dist/environment-provider-Dyg8DtLK.js.map +1 -0
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/graph-BJTxGOFB.js +471 -0
- package/dist/graph-BJTxGOFB.js.map +1 -0
- package/dist/{improvement-cycle-IJgCbKWQ.js → improvement-cycle-Csp38cWg.js} +133 -224
- package/dist/improvement-cycle-Csp38cWg.js.map +1 -0
- package/dist/{index-EdjCQBV9.d.ts → index-CoO7atyo.d.ts} +640 -1184
- package/dist/{index-DIV33AF5.d.ts → index-DwGtu9nc.d.ts} +7 -9
- package/dist/{index-Efjb3nrQ.d.ts → index-qYHpsmG2.d.ts} +36 -18
- package/dist/index.d.ts +353 -11
- package/dist/index.js +111 -354
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +6 -6
- package/dist/intelligence.js +9 -8
- package/dist/intelligence.js.map +1 -1
- package/dist/kernel.d.ts +7 -5
- package/dist/kernel.js +13 -9
- package/dist/{knowledge-EnuEqm_Y.js → knowledge-ce0_uKCl.js} +19 -17
- package/dist/knowledge-ce0_uKCl.js.map +1 -0
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{loop-runner-bin-Bo29_fiD.d.ts → loop-runner-bin-BwgZ8m_7.d.ts} +6 -3
- package/dist/{loop-runner-bin-qwT_4F5I.js → loop-runner-bin-DSbuDDqM.js} +5 -27
- package/dist/loop-runner-bin-DSbuDDqM.js.map +1 -0
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/materialization-COJ1UYQ-.js +272 -0
- package/dist/materialization-COJ1UYQ-.js.map +1 -0
- package/dist/mcp/bin.js +39 -47
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +24 -30
- package/dist/mcp/index.js +66 -83
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/memory-bin.js +1 -1
- package/dist/{memory-server-DL6cE2Ag.js → memory-server-5HEJH672.js} +2 -2
- package/dist/{memory-server-DL6cE2Ag.js.map → memory-server-5HEJH672.js.map} +1 -1
- package/dist/model-policy-CqziaqS1.js +232 -0
- package/dist/model-policy-CqziaqS1.js.map +1 -0
- package/dist/{openai-tools-B68JaOCx.d.ts → openai-tools-D3oMyVGl.d.ts} +2 -2
- package/dist/{openai-tools-Bp1KSkP6.js → openai-tools-ru75mLjq.js} +2 -2
- package/dist/openai-tools-ru75mLjq.js.map +1 -0
- package/dist/{prepare--8EvLqCr.js → prepare-DYWjVcPx.js} +169 -153
- package/dist/prepare-DYWjVcPx.js.map +1 -0
- package/dist/primeintellect/index.d.ts +7 -6
- package/dist/primeintellect/index.js +9 -11
- package/dist/primeintellect/index.js.map +1 -1
- package/dist/profiles.d.ts +21 -174
- package/dist/profiles.js +67 -276
- package/dist/profiles.js.map +1 -1
- package/dist/{protected-model-port-CXVfOUu_.js → protected-model-port-48ALLGxT.js} +2 -2
- package/dist/{protected-model-port-CXVfOUu_.js.map → protected-model-port-48ALLGxT.js.map} +1 -1
- package/dist/{redact-PQzmE1Jn.d.ts → redact-9_Gf-8m3.d.ts} +58 -69
- package/dist/{researcher-CoVqNhfI.js → researcher-Skz5-Uc8.js} +50 -26
- package/dist/researcher-Skz5-Uc8.js.map +1 -0
- package/dist/{run-layout-C2FGmZ3v.js → run-layout-CeJsEAom.js} +2 -2
- package/dist/{run-layout-C2FGmZ3v.js.map → run-layout-CeJsEAom.js.map} +1 -1
- package/dist/runtime-D-QfLbSd.d.ts +893 -0
- package/dist/{runtime-BzXz7OjS.js → runtime-hiAABiTk.js} +329 -854
- package/dist/runtime-hiAABiTk.js.map +1 -0
- package/dist/{sandbox-events-Yhd1GYWl.js → sandbox-events-CRDwc5WN.js} +42 -5
- package/dist/sandbox-events-CRDwc5WN.js.map +1 -0
- package/dist/snapshot-CXiiuHhL.js +21 -0
- package/dist/snapshot-CXiiuHhL.js.map +1 -0
- package/dist/{spawn-journal-DsZKDqeh.js → spawn-journal-saHQzqYi.js} +15 -21
- package/dist/spawn-journal-saHQzqYi.js.map +1 -0
- package/dist/stream-agent-turn-C852AgMT.d.ts +128 -0
- package/dist/stream-agent-turn-rYgaOLO0.js +910 -0
- package/dist/stream-agent-turn-rYgaOLO0.js.map +1 -0
- package/dist/{structural-rollout-zY0oqQzO.js → structural-rollout-3uxVGcg2.js} +459 -235
- package/dist/structural-rollout-3uxVGcg2.js.map +1 -0
- package/dist/{supervise-Ds8FtyI9.js → supervise-iPN27pO0.js} +932 -4771
- package/dist/supervise-iPN27pO0.js.map +1 -0
- package/dist/{supervisor-DpjO0Gmy.js → supervisor-CV6Jh28D.js} +7439 -2897
- package/dist/supervisor-CV6Jh28D.js.map +1 -0
- package/dist/testing.d.ts +3 -1
- package/dist/testing.js +271 -221
- package/dist/testing.js.map +1 -1
- package/dist/{top-app-5unxqovu.js → top-app-G4M18b6i.js} +3 -3
- package/dist/{top-app-5unxqovu.js.map → top-app-G4M18b6i.js.map} +1 -1
- package/dist/tui/bin.js +1 -1
- package/dist/tui/index.js +1 -1
- package/dist/{environment-provider-PM9PeW_J.d.ts → types-C6Q-J0Dt.d.ts} +57 -114
- package/dist/{types-DnNGJ5Gz.d.ts → types-ebIY0dMG.d.ts} +556 -30
- package/dist/{util-MVgdwuIS.js → util-Bw6srryQ.js} +3 -2
- package/dist/{util-MVgdwuIS.js.map → util-Bw6srryQ.js.map} +1 -1
- package/dist/{workspace-archive-BQxvkypI.js → workspace-archive-aOfJ47ms.js} +3 -3
- package/dist/{workspace-archive-BQxvkypI.js.map → workspace-archive-aOfJ47ms.js.map} +1 -1
- package/package.json +12 -15
- package/skills/agent-graphs/IMPROVE.md +58 -0
- package/skills/agent-graphs/SKILL.md +139 -0
- package/skills/agent-graphs/cases/artifact-mission-release-notes.json +10 -0
- package/skills/agent-graphs/cases/audited-single-writer.json +9 -0
- package/skills/agent-graphs/cases/cap-as-stop-mistake.json +8 -0
- package/skills/agent-graphs/cases/mission-in-deliverable.json +8 -0
- package/skills/agent-graphs/cases/review-pipeline.json +13 -0
- package/skills/agent-graphs/cases/runtime-discovered-fanout.json +8 -0
- package/skills/agent-graphs/cases/single-agent-suffices.json +7 -0
- package/skills/agent-graphs/cases/steer-heavy-drafting.json +9 -0
- package/skills/agent-graphs/cases/unmeasured-harness.json +7 -0
- package/skills/agent-graphs/generations/gen1-baseline.json +248 -0
- package/skills/agent-graphs/generations/gen2.json +375 -0
- package/skills/agent-graphs/generations/gen3.json +702 -0
- package/skills/build-with-agent-runtime/SKILL.md +1 -0
- package/dist/backends-CiOCyRHb.js +0 -743
- package/dist/backends-CiOCyRHb.js.map +0 -1
- package/dist/conversation-BpLQZGPH.js.map +0 -1
- package/dist/environment-provider-DqFS6FSZ.js.map +0 -1
- package/dist/improvement-cycle-IJgCbKWQ.js.map +0 -1
- package/dist/index-D_M4d1_B.d.ts +0 -545
- package/dist/knowledge-EnuEqm_Y.js.map +0 -1
- package/dist/local-harness-BIajef4A.d.ts +0 -465
- package/dist/loop-runner-bin-qwT_4F5I.js.map +0 -1
- package/dist/model-resolution-Btd9iIKV.js +0 -98
- package/dist/model-resolution-Btd9iIKV.js.map +0 -1
- package/dist/openai-tools-Bp1KSkP6.js.map +0 -1
- package/dist/prepare--8EvLqCr.js.map +0 -1
- package/dist/researcher-CoVqNhfI.js.map +0 -1
- package/dist/runtime-BzXz7OjS.js.map +0 -1
- package/dist/sandbox-events-Yhd1GYWl.js.map +0 -1
- package/dist/spawn-journal-DsZKDqeh.js.map +0 -1
- package/dist/structural-rollout-zY0oqQzO.js.map +0 -1
- package/dist/supervise-Ds8FtyI9.js.map +0 -1
- package/dist/supervisor-DpjO0Gmy.js.map +0 -1
- package/dist/types-C9j4qg6l.d.ts +0 -500
|
@@ -1,24 +1,54 @@
|
|
|
1
|
+
import { i as ConfigError } from "./errors-DEAvWQPy.js";
|
|
2
|
+
import { t as assertExecutableAgentProfile } from "./model-policy-CqziaqS1.js";
|
|
3
|
+
import { agentProfileSchema } from "@tangle-network/agent-interface";
|
|
1
4
|
//#region src/profiles/researcher.ts
|
|
2
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Opinionated preset for source-grounded research
|
|
7
|
+
* tasks. The agent is told to:
|
|
8
|
+
* - bound its work to a single `knowledgeNamespace`
|
|
9
|
+
* - emit `items[]` carrying provenance + confidence
|
|
10
|
+
* - emit `citations[]` linking quotes back to source urls
|
|
11
|
+
* - emit `proposedWrites[]` — never call materialize itself
|
|
12
|
+
* - describe `gaps` it could not answer
|
|
13
|
+
*
|
|
14
|
+
* The profile is stateless and agent-agnostic. The caller supplies the exact execution profile;
|
|
15
|
+
* this preset adds only the researcher prompt, tools, parser, and validator.
|
|
16
|
+
*
|
|
17
|
+
* Propose-don't-apply: the profile NEVER writes to the knowledge base.
|
|
18
|
+
* It produces `proposedWrites: KnowledgeUpdate[]` in the output. The
|
|
19
|
+
* caller (gtm-agent, journey-eval, user) decides whether to feed those
|
|
20
|
+
* updates through `applyKnowledgeWriteBlocks` / a KbStore put.
|
|
21
|
+
*
|
|
22
|
+
* Namespace isolation: every `KnowledgeItem` + `KnowledgeUpdate` in the
|
|
23
|
+
* output carries `namespace`. The validator hard-fails when any item
|
|
24
|
+
* touches a namespace other than `task.knowledgeNamespace`.
|
|
25
|
+
*
|
|
26
|
+
* @experimental
|
|
27
|
+
*/
|
|
3
28
|
const DEFAULT_CITATION_DENSITY_MIN = .7;
|
|
4
29
|
/** Build a source-grounded researcher profile with output parsing and validation. @experimental */
|
|
5
|
-
function researcherProfile(options
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const
|
|
30
|
+
function researcherProfile(options) {
|
|
31
|
+
const base = agentProfileSchema.parse(options.profile);
|
|
32
|
+
assertExecutableAgentProfile(base, "researcherProfile");
|
|
33
|
+
const name = options.name ?? base.name;
|
|
34
|
+
const systemPrompt = options.systemPrompt ?? RESEARCHER_SYSTEM_PROMPT;
|
|
9
35
|
const citationDensityMin = options.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN;
|
|
10
36
|
const profile = {
|
|
37
|
+
...base,
|
|
11
38
|
name,
|
|
12
|
-
description: "Source-grounded research agent. Propose-don't-apply.",
|
|
13
|
-
prompt: {
|
|
14
|
-
|
|
39
|
+
description: base.description ?? "Source-grounded research agent. Propose-don't-apply.",
|
|
40
|
+
prompt: {
|
|
41
|
+
...base.prompt,
|
|
42
|
+
systemPrompt
|
|
43
|
+
},
|
|
15
44
|
tools: {
|
|
16
45
|
web_search: true,
|
|
17
46
|
fs: true,
|
|
18
|
-
shell: true
|
|
47
|
+
shell: true,
|
|
48
|
+
...base.tools
|
|
19
49
|
},
|
|
20
50
|
metadata: {
|
|
21
|
-
|
|
51
|
+
...base.metadata,
|
|
22
52
|
role: "researcher"
|
|
23
53
|
}
|
|
24
54
|
};
|
|
@@ -47,21 +77,14 @@ function researcherProfile(options = {}) {
|
|
|
47
77
|
*
|
|
48
78
|
* @experimental
|
|
49
79
|
*/
|
|
50
|
-
function multiHarnessResearcherFanout(options
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
"codex"
|
|
55
|
-
];
|
|
56
|
-
const models = options.models ?? [];
|
|
57
|
-
const agentRuns = harnesses.map((harness, i) => {
|
|
58
|
-
const { agentRunSpec } = researcherProfile({
|
|
59
|
-
harness,
|
|
60
|
-
model: models[i]
|
|
61
|
-
});
|
|
80
|
+
function multiHarnessResearcherFanout(options) {
|
|
81
|
+
if (options.profiles.length === 0) throw new ConfigError("multiHarnessResearcherFanout: at least one exact profile is required");
|
|
82
|
+
const agentRuns = options.profiles.map((profile) => {
|
|
83
|
+
const { agentRunSpec } = researcherProfile({ profile });
|
|
62
84
|
return agentRunSpec;
|
|
63
85
|
});
|
|
64
86
|
const { output, validator } = researcherProfile({
|
|
87
|
+
profile: options.profiles[0],
|
|
65
88
|
citationDensityMin: options.citationDensityMin,
|
|
66
89
|
task: options.task
|
|
67
90
|
});
|
|
@@ -72,7 +95,7 @@ function multiHarnessResearcherFanout(options = {}) {
|
|
|
72
95
|
driver: {
|
|
73
96
|
name: "researcher-fanout",
|
|
74
97
|
async plan(task, history) {
|
|
75
|
-
return history.length === 0 ? Array.from({ length:
|
|
98
|
+
return history.length === 0 ? Array.from({ length: agentRuns.length }, () => task) : [];
|
|
76
99
|
},
|
|
77
100
|
decide() {
|
|
78
101
|
return "done";
|
|
@@ -168,7 +191,8 @@ function recencyMatchScore(items, window) {
|
|
|
168
191
|
}
|
|
169
192
|
return total === 0 ? 0 : hits / total;
|
|
170
193
|
}
|
|
171
|
-
|
|
194
|
+
/** Built-in source-grounded research contract added to a caller-owned exact profile. */
|
|
195
|
+
const RESEARCHER_SYSTEM_PROMPT = [
|
|
172
196
|
"You are a research agent. Your job is to answer a research question with",
|
|
173
197
|
"source-grounded knowledge items that the caller will choose whether to",
|
|
174
198
|
"persist to a multi-tenant knowledge base.",
|
|
@@ -428,6 +452,6 @@ function clamp01(value) {
|
|
|
428
452
|
return value;
|
|
429
453
|
}
|
|
430
454
|
//#endregion
|
|
431
|
-
export {
|
|
455
|
+
export { researcherProfile as i, createResearcherValidator as n, multiHarnessResearcherFanout as r, RESEARCHER_SYSTEM_PROMPT as t };
|
|
432
456
|
|
|
433
|
-
//# sourceMappingURL=researcher-
|
|
457
|
+
//# sourceMappingURL=researcher-Skz5-Uc8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"researcher-Skz5-Uc8.js","names":[],"sources":["../src/profiles/researcher.ts"],"sourcesContent":["/**\n * Opinionated preset for source-grounded research\n * tasks. The agent is told to:\n * - bound its work to a single `knowledgeNamespace`\n * - emit `items[]` carrying provenance + confidence\n * - emit `citations[]` linking quotes back to source urls\n * - emit `proposedWrites[]` — never call materialize itself\n * - describe `gaps` it could not answer\n *\n * The profile is stateless and agent-agnostic. The caller supplies the exact execution profile;\n * this preset adds only the researcher prompt, tools, parser, and validator.\n *\n * Propose-don't-apply: the profile NEVER writes to the knowledge base.\n * It produces `proposedWrites: KnowledgeUpdate[]` in the output. The\n * caller (gtm-agent, journey-eval, user) decides whether to feed those\n * updates through `applyKnowledgeWriteBlocks` / a KbStore put.\n *\n * Namespace isolation: every `KnowledgeItem` + `KnowledgeUpdate` in the\n * output carries `namespace`. The validator hard-fails when any item\n * touches a namespace other than `task.knowledgeNamespace`.\n *\n * @experimental\n */\n\nimport { type AgentProfile, agentProfileSchema } from '@tangle-network/agent-interface'\nimport type { SandboxEvent } from '@tangle-network/sandbox'\nimport { ConfigError } from '../errors'\nimport { assertExecutableAgentProfile } from '../runtime/supervise/model-policy'\nimport type {\n AgentRunSpec,\n DefaultVerdict,\n Driver,\n OutputAdapter,\n Validator,\n} from '../runtime/types'\n\n/** Source families a researcher profile may prefer for a task. @experimental */\nexport type ResearchSource = 'web' | 'corpus' | 'twitter' | 'github' | 'docs'\n\n/** Task contract for a source-grounded research agent. @experimental */\nexport interface ResearchTask {\n /** The research question to answer. */\n question: string\n /** Bound: e.g. \"audience for cpg-founder ICP\". */\n scope?: string\n /** Multi-tenant scope (customer-id, workspace-id). Validator enforces. */\n knowledgeNamespace: string\n sources?: ResearchSource[]\n recencyWindow?: { since?: Date; until?: Date }\n maxItems?: number\n /** Per-item minimum confidence in [0, 1]. Validator scores recall vs this. */\n minConfidence?: number\n}\n\n/**\n * Knowledge item emitted by the researcher.\n *\n * Profile-local type. When agent-knowledge promotes `KnowledgeClaim` →\n * top-level `KnowledgeItem` substrate-wide, these fields collapse 1:1.\n *\n * @experimental\n */\nexport interface KnowledgeItem {\n id: string\n /** Multi-tenant scope. MUST equal `task.knowledgeNamespace`. */\n namespace: string\n /** The factual claim, in the researcher's words. */\n claim: string\n /** Provenance — at least one entry required. */\n evidence: Array<{ source: string; quote?: string; url?: string; capturedAt: number }>\n /** Researcher's self-reported confidence in [0, 1]. */\n confidence: number\n /** Prior item ids this supersedes (chain). */\n supersedes?: string[]\n /** Set if the agent is retracting an earlier item. Unix ms. */\n retractedAt?: number\n authoredBy: { kind: 'human' | 'agent'; id: string }\n}\n\n/**\n * A proposed write to the knowledge base. The profile does NOT apply\n * these — the caller decides.\n *\n * @experimental\n */\nexport type KnowledgeUpdate =\n | { kind: 'insert'; namespace: string; item: KnowledgeItem }\n | { kind: 'supersede'; namespace: string; previousId: string; item: KnowledgeItem }\n | { kind: 'retract'; namespace: string; itemId: string; reason: string }\n\n/**\n * Researcher output. Required fields are typed; optional fields preserve\n * the agent's free-form intelligence (`notes`, `raw`). The validator\n * enforces the typed minimum.\n *\n * @experimental\n */\nexport interface ResearchOutput {\n items: KnowledgeItem[]\n citations: Array<{ url: string; quote: string; confidence: number }>\n proposedWrites: KnowledgeUpdate[]\n gaps?: string[]\n notes?: string\n /** Anything the agent emitted beyond the typed fields. */\n raw?: unknown\n}\n\n/** Options for the source-grounded researcher profile preset. @experimental */\nexport interface ResearcherProfileOptions {\n /** Caller-owned exact harness/provider/model identity. */\n profile: AgentProfile\n /** Custom system prompt replacement. Default = built-in researcher preset. */\n systemPrompt?: string\n /** Stable name for `AgentRunSpec.name`. Default = `profile.name`. */\n name?: string\n /**\n * Default 0.7. Minimum (citations with quote) / items ratio for `valid=true`.\n * Below this floor, citation_density scores < 1 and the item set is gated.\n */\n citationDensityMin?: number\n}\n\nconst DEFAULT_CITATION_DENSITY_MIN = 0.7\n\n/** Build a source-grounded researcher profile with output parsing and validation. @experimental */\nexport function researcherProfile(options: ResearcherProfileOptions & { task?: ResearchTask }): {\n profile: AgentProfile\n taskToPrompt: (task: ResearchTask) => string\n output: OutputAdapter<ResearchOutput>\n validator: Validator<ResearchOutput>\n agentRunSpec: AgentRunSpec<ResearchTask>\n} {\n const base = agentProfileSchema.parse(options.profile) as AgentProfile\n assertExecutableAgentProfile(base, 'researcherProfile')\n const name = options.name ?? base.name\n const systemPrompt = options.systemPrompt ?? RESEARCHER_SYSTEM_PROMPT\n const citationDensityMin = options.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN\n const profile: AgentProfile = {\n ...base,\n name,\n description: base.description ?? \"Source-grounded research agent. Propose-don't-apply.\",\n prompt: { ...base.prompt, systemPrompt },\n tools: { web_search: true, fs: true, shell: true, ...base.tools },\n metadata: { ...base.metadata, role: 'researcher' },\n }\n const output: OutputAdapter<ResearchOutput> = { parse: parseResearcherEvents }\n const validator: Validator<ResearchOutput> = options.task\n ? createResearcherValidator(options.task, { citationDensityMin })\n : createResearcherValidator(\n { question: '', knowledgeNamespace: '' },\n { citationDensityMin, namespaceCheck: false },\n )\n const agentRunSpec: AgentRunSpec<ResearchTask> = {\n name,\n profile,\n taskToPrompt: formatResearcherPrompt,\n }\n return { profile, taskToPrompt: formatResearcherPrompt, output, validator, agentRunSpec }\n}\n\n/** @experimental */\nexport interface MultiHarnessResearcherFanoutOptions {\n /** Exact execution profiles, one per parallel researcher. */\n profiles: ReadonlyArray<AgentProfile>\n /** Default citation density floor for the shared validator. */\n citationDensityMin?: number\n /** Optional task — narrows the validator's namespace check. */\n task?: ResearchTask\n}\n\n/**\n * Build a fanout topology over multiple harnesses. The kernel round-robins\n * `agentRuns` across the N parallel iterations and the `FanoutVote` driver\n * picks the highest-scoring valid output.\n *\n * @experimental\n */\nexport function multiHarnessResearcherFanout(options: MultiHarnessResearcherFanoutOptions): {\n agentRuns: AgentRunSpec<ResearchTask>[]\n output: OutputAdapter<ResearchOutput>\n validator: Validator<ResearchOutput>\n driver: Driver<ResearchTask, ResearchOutput, 'done'>\n} {\n if (options.profiles.length === 0) {\n throw new ConfigError('multiHarnessResearcherFanout: at least one exact profile is required')\n }\n const agentRuns = options.profiles.map((profile) => {\n const { agentRunSpec } = researcherProfile({ profile })\n return agentRunSpec\n })\n const { output, validator } = researcherProfile({\n profile: options.profiles[0]!,\n citationDensityMin: options.citationDensityMin,\n task: options.task,\n })\n // Single fanout round across the N harnesses, then stop: the kernel\n // round-robins `agentRuns` over the N branches and selects the winner\n // (best valid score) across all iterations via `defaultSelectWinner`.\n const driver: Driver<ResearchTask, ResearchOutput, 'done'> = {\n name: 'researcher-fanout',\n async plan(task, history) {\n return history.length === 0 ? Array.from({ length: agentRuns.length }, () => task) : []\n },\n // 'done' is a terminal decision in the kernel; the loop finalizes after\n // the single fanout round and selects the winner via `defaultSelectWinner`.\n decide() {\n return 'done'\n },\n describePlan() {\n return { kind: 'fanout' }\n },\n }\n return { agentRuns, output, validator, driver }\n}\n\n/**\n * Build a validator that closes over a specific `ResearchTask`'s constraints.\n *\n * Checks in order:\n * 1. Items must be non-empty.\n * 2. Every item carries `evidence.length >= 1`.\n * 3. Every item + proposedWrite is scoped to `task.knowledgeNamespace`\n * (hard-fail on any namespace mismatch — defence in depth for the\n * multi-tenant invariant).\n * 4. Citation density (citations with quote / items) >= floor.\n *\n * Aggregate score:\n * 0.4 · citation_density\n * + 0.2 · source_diversity (distinct sources / max(items, 1))\n * + 0.2 · recency_match (mean fraction within `recencyWindow`)\n * + 0.2 · (1 − gaps/maxGaps), maxGaps = max(items, 1)\n *\n * @experimental\n */\nexport function createResearcherValidator(\n task: ResearchTask,\n config: { citationDensityMin?: number; namespaceCheck?: boolean } = {},\n): Validator<ResearchOutput> {\n const citationDensityMin = config.citationDensityMin ?? DEFAULT_CITATION_DENSITY_MIN\n const namespaceCheck = config.namespaceCheck ?? true\n return {\n async validate(output) {\n const notes: string[] = []\n const scores: Record<string, number> = {}\n let pass = true\n\n if (!Array.isArray(output.items) || output.items.length === 0) {\n pass = false\n notes.push('no items')\n scores.items = 0\n } else {\n scores.items = 1\n }\n\n const missingEvidence = (output.items ?? []).filter(\n (item) => !Array.isArray(item.evidence) || item.evidence.length === 0,\n )\n if (missingEvidence.length > 0) {\n pass = false\n notes.push(`${missingEvidence.length} item(s) without evidence`)\n scores.provenance = 0\n } else {\n scores.provenance = 1\n }\n\n if (namespaceCheck) {\n const foreignItems = (output.items ?? []).filter(\n (item) => item.namespace !== task.knowledgeNamespace,\n )\n const foreignWrites = (output.proposedWrites ?? []).filter(\n (write) => write.namespace !== task.knowledgeNamespace,\n )\n if (foreignItems.length > 0 || foreignWrites.length > 0) {\n pass = false\n notes.push(\n `namespace violation: ${foreignItems.length} item(s) + ${foreignWrites.length} write(s) ` +\n `outside ${task.knowledgeNamespace}`,\n )\n scores.namespace = 0\n } else {\n scores.namespace = 1\n }\n }\n\n const itemCount = Math.max(output.items?.length ?? 0, 1)\n const citationsWithQuote = (output.citations ?? []).filter(\n (citation) => typeof citation.quote === 'string' && citation.quote.length > 0,\n ).length\n const citationDensity = Math.min(1, citationsWithQuote / itemCount)\n if (citationDensity < citationDensityMin) {\n pass = false\n notes.push(\n `citation density ${citationDensity.toFixed(2)} below floor ${citationDensityMin.toFixed(2)}`,\n )\n }\n scores.citation_density = citationDensity\n\n const sourceSet = new Set<string>()\n for (const item of output.items ?? []) {\n for (const evidence of item.evidence ?? []) {\n if (evidence.source) sourceSet.add(evidence.source)\n }\n }\n scores.source_diversity = Math.min(1, sourceSet.size / itemCount)\n\n scores.recency_match = recencyMatchScore(output.items ?? [], task.recencyWindow)\n\n const maxGaps = itemCount\n const gapCount = output.gaps?.length ?? 0\n scores.gap_coverage = Math.max(0, 1 - gapCount / maxGaps)\n\n const score =\n 0.4 * scores.citation_density +\n 0.2 * scores.source_diversity +\n 0.2 * scores.recency_match +\n 0.2 * scores.gap_coverage\n\n const verdict: DefaultVerdict = {\n valid: pass,\n score: Number.isFinite(score) ? score : 0,\n scores,\n }\n if (notes.length > 0) verdict.notes = notes.join('; ')\n return verdict\n },\n }\n}\n\nfunction recencyMatchScore(items: KnowledgeItem[], window: ResearchTask['recencyWindow']): number {\n if (!window || (window.since === undefined && window.until === undefined)) return 1\n if (items.length === 0) return 0\n const sinceMs = window.since?.getTime() ?? Number.NEGATIVE_INFINITY\n const untilMs = window.until?.getTime() ?? Number.POSITIVE_INFINITY\n let hits = 0\n let total = 0\n for (const item of items) {\n for (const evidence of item.evidence ?? []) {\n if (typeof evidence.capturedAt !== 'number') continue\n total += 1\n if (evidence.capturedAt >= sinceMs && evidence.capturedAt <= untilMs) hits += 1\n }\n }\n return total === 0 ? 0 : hits / total\n}\n\n/** Built-in source-grounded research contract added to a caller-owned exact profile. */\nexport const RESEARCHER_SYSTEM_PROMPT = [\n 'You are a research agent. Your job is to answer a research question with',\n 'source-grounded knowledge items that the caller will choose whether to',\n 'persist to a multi-tenant knowledge base.',\n '',\n 'Hard rules:',\n \" 1. Every item you emit MUST carry the task's knowledgeNamespace exactly.\",\n ' Never write to a different namespace.',\n ' 2. Every item MUST carry at least one evidence entry with a source.',\n ' A quote + url is strongly preferred; capturedAt is unix ms.',\n ' 3. You propose writes — you do NOT apply them. The caller decides.',\n ' 4. Self-report confidence honestly in [0, 1]. Do not inflate.',\n ' 5. List what you could not answer in `gaps`. Better to admit a gap',\n ' than fabricate.',\n '',\n 'When you finish, emit a single final structured message of the shape:',\n ' ```json',\n ' { \"items\": [{ \"id\": \"...\", \"namespace\": \"...\", \"claim\": \"...\",',\n ' \"evidence\": [{ \"source\": \"...\", \"quote\": \"...\",',\n ' \"url\": \"...\", \"capturedAt\": 0 }],',\n ' \"confidence\": 0.0,',\n ' \"authoredBy\": { \"kind\": \"agent\", \"id\": \"...\" } }],',\n ' \"citations\": [{ \"url\": \"...\", \"quote\": \"...\", \"confidence\": 0.0 }],',\n ' \"proposedWrites\": [{ \"kind\": \"insert\", \"namespace\": \"...\",',\n ' \"item\": { /* same shape as items[] */ } }],',\n ' \"gaps\": [\"...\"],',\n ' \"notes\": \"free-form commentary\" }',\n ' ```',\n].join('\\n')\n\nfunction formatResearcherPrompt(task: ResearchTask): string {\n const sources = task.sources?.length ? task.sources.join(', ') : '(no preference)'\n const window = formatRecencyWindow(task.recencyWindow)\n return [\n `Question: ${task.question}`,\n `Knowledge namespace (DO NOT cross): ${task.knowledgeNamespace}`,\n `Scope: ${task.scope ?? '(unspecified)'}`,\n `Preferred sources: ${sources}`,\n `Recency window: ${window}`,\n `Max items: ${task.maxItems ?? '(no cap)'}`,\n `Per-item minimum confidence: ${task.minConfidence ?? '(no floor)'}`,\n '',\n 'Produce knowledge items with provenance + citations + proposed writes.',\n 'List gaps for anything you could not answer. Emit the final JSON',\n 'result block exactly as instructed.',\n ].join('\\n')\n}\n\nfunction formatRecencyWindow(window: ResearchTask['recencyWindow']): string {\n if (!window) return '(none)'\n const since = window.since ? window.since.toISOString() : '-∞'\n const until = window.until ? window.until.toISOString() : 'now'\n return `${since} .. ${until}`\n}\n\n/**\n * Walk the event stream and return the last structured `research.result`\n * payload. Falls back to scanning text deltas for a fenced JSON block.\n */\nfunction parseResearcherEvents(events: SandboxEvent[]): ResearchOutput {\n for (let i = events.length - 1; i >= 0; i -= 1) {\n const event = events[i]\n if (!event) continue\n const type = String(event.type ?? '')\n const data = isRecord(event.data) ? event.data : {}\n if (type === 'result' || type === 'final' || type === 'research.result') {\n const direct = coerceResearchOutput(data.result ?? data.output ?? data)\n // opencode reports the agent's terminal answer in `finalText` (not result/output).\n // Parse it too, and prefer it when `direct` is an all-empty shape — otherwise an\n // event carrying both `{items:[],citations:[],proposedWrites:[]}` and a rich\n // finalText would silently drop the real answer.\n const finalText = pickString(data.finalText)\n const fenced = finalText ? extractFencedJson(finalText) : undefined\n const fromFinalText = fenced ? coerceResearchOutput(fenced) : undefined\n if (direct && !isEmptyOutput(direct)) return direct\n if (fromFinalText) return fromFinalText\n if (direct) return direct\n }\n }\n for (let i = events.length - 1; i >= 0; i -= 1) {\n const event = events[i]\n if (!event) continue\n const data = isRecord(event.data) ? event.data : {}\n const text = pickString(data.text) ?? pickString(data.delta) ?? pickString(data.finalText)\n if (!text) continue\n const fenced = extractFencedJson(text)\n if (!fenced) continue\n const coerced = coerceResearchOutput(fenced)\n if (coerced) return coerced\n }\n return { items: [], citations: [], proposedWrites: [] }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value)\n}\n\n/** A coerced output that carries no items, citations, or proposed writes. */\nfunction isEmptyOutput(o: ResearchOutput): boolean {\n return o.items.length === 0 && o.citations.length === 0 && o.proposedWrites.length === 0\n}\n\nfunction pickString(value: unknown): string | undefined {\n return typeof value === 'string' && value.length > 0 ? value : undefined\n}\n\nfunction extractFencedJson(text: string): unknown | undefined {\n const match = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/i)\n if (!match) return undefined\n const body = (match[1] ?? '').trim()\n if (!body) return undefined\n try {\n return JSON.parse(body)\n } catch {\n return undefined\n }\n}\n\nfunction coerceResearchOutput(value: unknown): ResearchOutput | undefined {\n if (!isRecord(value)) return undefined\n const items = coerceItems(value.items)\n const citations = coerceCitations(value.citations)\n const proposedWrites = coerceProposedWrites(value.proposedWrites)\n // Reject completely empty payloads — those signal \"no result block\",\n // not \"a valid empty result\".\n if (items === undefined && citations === undefined && proposedWrites === undefined) {\n return undefined\n }\n const output: ResearchOutput = {\n items: items ?? [],\n citations: citations ?? [],\n proposedWrites: proposedWrites ?? [],\n }\n if (Array.isArray(value.gaps)) {\n output.gaps = value.gaps.filter((entry): entry is string => typeof entry === 'string')\n }\n const notes = pickString(value.notes)\n if (notes) output.notes = notes\n // Preserve any extra fields the agent emitted that don't fit the typed\n // surface — callers can inspect `raw` without forcing them through the\n // typed coercion. We only include `raw` when the agent emitted fields\n // beyond the known set.\n const known = new Set(['items', 'citations', 'proposedWrites', 'gaps', 'notes'])\n const extras: Record<string, unknown> = {}\n let extrasCount = 0\n for (const [key, val] of Object.entries(value)) {\n if (known.has(key)) continue\n extras[key] = val\n extrasCount += 1\n }\n if (extrasCount > 0) output.raw = extras\n return output\n}\n\nfunction coerceItems(value: unknown): KnowledgeItem[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: KnowledgeItem[] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const id = pickString(entry.id)\n const namespace = pickString(entry.namespace)\n const claim = pickString(entry.claim)\n const evidence = coerceEvidence(entry.evidence)\n const confidence = toFiniteNumber(entry.confidence)\n const authoredBy = coerceAuthoredBy(entry.authoredBy)\n if (!id || !namespace || !claim || !authoredBy) continue\n const item: KnowledgeItem = {\n id,\n namespace,\n claim,\n evidence,\n confidence: clamp01(confidence),\n authoredBy,\n }\n if (Array.isArray(entry.supersedes)) {\n item.supersedes = entry.supersedes.filter((s): s is string => typeof s === 'string')\n }\n const retractedAt = toFiniteNumber(entry.retractedAt)\n if (retractedAt > 0) item.retractedAt = retractedAt\n out.push(item)\n }\n return out\n}\n\nfunction coerceEvidence(value: unknown): KnowledgeItem['evidence'] {\n if (!Array.isArray(value)) return []\n const out: KnowledgeItem['evidence'] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const source = pickString(entry.source)\n if (!source) continue\n const item: KnowledgeItem['evidence'][number] = {\n source,\n capturedAt: toFiniteNumber(entry.capturedAt),\n }\n const quote = pickString(entry.quote)\n if (quote) item.quote = quote\n const url = pickString(entry.url)\n if (url) item.url = url\n out.push(item)\n }\n return out\n}\n\nfunction coerceAuthoredBy(value: unknown): KnowledgeItem['authoredBy'] | undefined {\n if (!isRecord(value)) return undefined\n const kind = value.kind === 'human' || value.kind === 'agent' ? value.kind : undefined\n const id = pickString(value.id)\n if (!kind || !id) return undefined\n return { kind, id }\n}\n\nfunction coerceCitations(value: unknown): ResearchOutput['citations'] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: ResearchOutput['citations'] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const url = pickString(entry.url)\n const quote = pickString(entry.quote)\n if (!url || !quote) continue\n out.push({ url, quote, confidence: clamp01(toFiniteNumber(entry.confidence)) })\n }\n return out\n}\n\nfunction coerceProposedWrites(value: unknown): KnowledgeUpdate[] | undefined {\n if (!Array.isArray(value)) return undefined\n const out: KnowledgeUpdate[] = []\n for (const entry of value) {\n if (!isRecord(entry)) continue\n const namespace = pickString(entry.namespace)\n if (!namespace) continue\n if (entry.kind === 'insert') {\n const items = coerceItems([entry.item])\n const item = items?.[0]\n if (!item) continue\n out.push({ kind: 'insert', namespace, item })\n } else if (entry.kind === 'supersede') {\n const previousId = pickString(entry.previousId)\n const items = coerceItems([entry.item])\n const item = items?.[0]\n if (!previousId || !item) continue\n out.push({ kind: 'supersede', namespace, previousId, item })\n } else if (entry.kind === 'retract') {\n const itemId = pickString(entry.itemId)\n const reason = pickString(entry.reason)\n if (!itemId || !reason) continue\n out.push({ kind: 'retract', namespace, itemId, reason })\n }\n }\n return out\n}\n\nfunction toFiniteNumber(value: unknown): number {\n return typeof value === 'number' && Number.isFinite(value) ? value : 0\n}\n\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0\n if (value < 0) return 0\n if (value > 1) return 1\n return value\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
2
|
import { join, resolve } from "node:path";
|
|
3
|
+
import { appendFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
4
4
|
//#region src/runtime/supervise/run-layout.ts
|
|
5
5
|
/**
|
|
6
6
|
* The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.
|
|
@@ -155,4 +155,4 @@ function isWorkerSteerRequest(value) {
|
|
|
155
155
|
//#endregion
|
|
156
156
|
export { supervisorRunDir as a, workerControlLogFile as c, writeWorkerSteer as d, safeWorkerFile as i, workerInboxFile as l, legacySupervisorRunsRoot as n, supervisorRunsRoot as o, readWorkerSteerRequests as r, supervisorWorkersDir as s, legacySupervisorRunDir as t, workerInboxFileFromEventDir as u };
|
|
157
157
|
|
|
158
|
-
//# sourceMappingURL=run-layout-
|
|
158
|
+
//# sourceMappingURL=run-layout-CeJsEAom.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"run-layout-C2FGmZ3v.js","names":[],"sources":["../src/runtime/supervise/run-layout.ts"],"sourcesContent":["/**\n * The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.\n *\n * This is the durable, cross-process face of a supervisor run — the counterpart to the in-process\n * `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process\n * can find it after the fact: `@tangle-network/traces` reads exactly this layout via\n * `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds\n * handles to, and a human can steer a live worker by appending one NDJSON line. Until now the\n * layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a\n * published reader depending on an unpublished writer's convention — so the contract is promoted\n * here, names preserved.\n *\n * `.agent` is the one dot-dir for ALL agent-owned state (skills already write\n * `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than\n * under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —\n * readers that must see those keep a legacy fallback; this writer never creates `.loops` again.\n *\n * Layout, relative to `supervisorRunDir(root, id)`:\n *\n * workers/<label>.inbox.ndjson down-leg steer/answer requests for one worker (durable inbox);\n * each line is a {@link WorkerSteerRequest}\n * workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)\n *\n * Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line\n * never poisons the rest of the file — later valid lines still matter.\n *\n * Promoted from `loops/src/supervisor-control.ts`. The one deliberate difference: the loops version\n * resolved a worker id to its label through the run journal before writing a steer; that resolution\n * stays with the caller (it is journal-format-specific), so `writeWorkerSteer` here takes the worker\n * LABEL directly.\n *\n * @experimental\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\n\n/** One durable down-leg request appended to a worker's inbox file. */\nexport interface WorkerSteerRequest {\n readonly id: string\n /** ISO timestamp of the append. */\n readonly at: string\n /** Who asked — 'human', a brain label, a tool name. Provenance, not authorization. */\n readonly source: string\n /** The worker LABEL the request targets (already resolved by the caller). */\n readonly worker: string\n readonly message: string\n}\n\n/** The root every supervisor run of one workspace lives under. */\nexport function supervisorRunsRoot(rootDir: string): string {\n return join(resolve(rootDir), '.agent', 'supervisor')\n}\n\n/** The run directory every artifact of one supervisor run lives under. */\nexport function supervisorRunDir(rootDir: string, id: string): string {\n return join(supervisorRunsRoot(rootDir), id)\n}\n\n/**\n * Where a pre-rename writer put the same run (`<root>/.loops/supervisor/<id>`). Readers that must\n * see historical runs check {@link supervisorRunDir} first and fall back to this; nothing writes\n * here anymore.\n */\nexport function legacySupervisorRunDir(rootDir: string, id: string): string {\n return join(legacySupervisorRunsRoot(rootDir), id)\n}\n\n/**\n * The pre-rename runs root (`<root>/.loops/supervisor`). Only readers that ENUMERATE historical\n * runs need this — the per-id form is {@link legacySupervisorRunDir}. Nothing writes here.\n */\nexport function legacySupervisorRunsRoot(rootDir: string): string {\n return join(resolve(rootDir), '.loops', 'supervisor')\n}\n\n/** A worker label reduced to a safe filename stem. Empty labels get a stable fallback. */\nexport function safeWorkerFile(label: string): string {\n const safe = label.replace(/[^A-Za-z0-9._-]/g, '_')\n return safe.length > 0 ? safe : 'worker'\n}\n\n/** The directory holding every per-worker file of one run (inboxes and control-event logs). */\nexport function supervisorWorkersDir(eventDir: string): string {\n return join(eventDir, 'workers')\n}\n\n/** The durable inbox file for one worker of one run. */\nexport function workerInboxFile(rootDir: string, supervisorId: string, worker: string): string {\n return workerInboxFileFromEventDir(supervisorRunDir(rootDir, supervisorId), worker)\n}\n\n/** Same, addressed from an already-known run directory (the reader's usual entry point). */\nexport function workerInboxFileFromEventDir(eventDir: string, worker: string): string {\n return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.inbox.ndjson`)\n}\n\n/**\n * The best-effort control-event log for one worker (`workers/<label>.ndjson`) — delivery\n * bookkeeping for steers, plus whatever lifecycle events a writer chooses to append. Distinct from\n * the inbox: the inbox is the durable down-leg queue, this is the record of what happened to it.\n */\nexport function workerControlLogFile(eventDir: string, worker: string): string {\n return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.ndjson`)\n}\n\n/**\n * Durably append one steer request to a worker's inbox and log the delivery attempt.\n *\n * The inbox append is the durable act; the control-event log is best-effort bookkeeping and may\n * silently fail without voiding the steer.\n */\nexport function writeWorkerSteer(\n rootDir: string,\n supervisorId: string,\n worker: string,\n message: string,\n source = 'human',\n): { worker: string; file: string; request: WorkerSteerRequest } {\n const trimmed = message.trim()\n if (!trimmed) throw new Error('steer message is empty')\n const dir = supervisorRunDir(rootDir, supervisorId)\n mkdirSync(supervisorWorkersDir(dir), { recursive: true })\n const request: WorkerSteerRequest = {\n id: randomUUID(),\n at: new Date().toISOString(),\n source,\n worker,\n message: trimmed,\n }\n const file = workerInboxFile(rootDir, supervisorId, worker)\n appendFileSync(file, `${JSON.stringify(request)}\\n`, 'utf8')\n appendWorkerControlEvent(dir, worker, {\n kind: 'message',\n direction: 'down',\n source,\n requestId: request.id,\n message: trimmed,\n queued: true,\n delivered: false,\n })\n return { worker, file, request }\n}\n\n/** Read every valid steer request in a worker's inbox. Corrupt or partial lines are skipped. */\nexport function readWorkerSteerRequests(eventDir: string, worker: string): WorkerSteerRequest[] {\n const file = workerInboxFileFromEventDir(eventDir, worker)\n if (!existsSync(file)) return []\n const out: WorkerSteerRequest[] = []\n let raw = ''\n try {\n raw = readFileSync(file, 'utf8')\n } catch {\n return out\n }\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n const parsed = JSON.parse(trimmed) as Partial<WorkerSteerRequest>\n if (isWorkerSteerRequest(parsed)) out.push(parsed)\n } catch {\n // Ignore partial or corrupt input lines; later valid lines still matter.\n }\n }\n return out\n}\n\nfunction appendWorkerControlEvent(\n eventDir: string,\n label: string,\n event: Record<string, unknown>,\n): void {\n try {\n mkdirSync(supervisorWorkersDir(eventDir), { recursive: true })\n appendFileSync(\n workerControlLogFile(eventDir, label),\n `${JSON.stringify({ at: new Date().toISOString(), label, ...event })}\\n`,\n 'utf8',\n )\n } catch {\n // Control logging is best-effort; the inbox write above is the durable part.\n }\n}\n\nfunction isWorkerSteerRequest(value: Partial<WorkerSteerRequest>): value is WorkerSteerRequest {\n return (\n typeof value.id === 'string' &&\n typeof value.at === 'string' &&\n typeof value.source === 'string' &&\n typeof value.worker === 'string' &&\n typeof value.message === 'string' &&\n value.message.trim().length > 0\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"run-layout-CeJsEAom.js","names":[],"sources":["../src/runtime/supervise/run-layout.ts"],"sourcesContent":["/**\n * The on-disk supervisor-run layout: `<root>/.agent/supervisor/<id>`.\n *\n * This is the durable, cross-process face of a supervisor run — the counterpart to the in-process\n * `Inbox` seam in `./inbox`. A run persists its state under one directory so that any OTHER process\n * can find it after the fact: `@tangle-network/traces` reads exactly this layout via\n * `traces analyze --supervisor-run-dir`, a restarted host can rehydrate a run it no longer holds\n * handles to, and a human can steer a live worker by appending one NDJSON line. Until now the\n * layout was defined only in the unpublished `loops` repo (`src/supervisor-control.ts`) — a\n * published reader depending on an unpublished writer's convention — so the contract is promoted\n * here, names preserved.\n *\n * `.agent` is the one dot-dir for ALL agent-owned state (skills already write\n * `.agent/hypotheses/`, `.agent/skill-runs.jsonl`); supervisor runs live beside them rather than\n * under a product-branded dir. Runs written by older writers used `.loops/supervisor/<id>` —\n * readers that must see those keep a legacy fallback; this writer never creates `.loops` again.\n *\n * Layout, relative to `supervisorRunDir(root, id)`:\n *\n * workers/<label>.inbox.ndjson down-leg steer/answer requests for one worker (durable inbox);\n * each line is a {@link WorkerSteerRequest}\n * workers/<label>.ndjson best-effort per-worker control-event log (delivery bookkeeping)\n *\n * Reads are tolerant by contract: a partial trailing line (a writer mid-append) or a corrupt line\n * never poisons the rest of the file — later valid lines still matter.\n *\n * Promoted from `loops/src/supervisor-control.ts`. The one deliberate difference: the loops version\n * resolved a worker id to its label through the run journal before writing a steer; that resolution\n * stays with the caller (it is journal-format-specific), so `writeWorkerSteer` here takes the worker\n * LABEL directly.\n *\n * @experimental\n */\n\nimport { randomUUID } from 'node:crypto'\nimport { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'\nimport { join, resolve } from 'node:path'\n\n/** One durable down-leg request appended to a worker's inbox file. */\nexport interface WorkerSteerRequest {\n readonly id: string\n /** ISO timestamp of the append. */\n readonly at: string\n /** Who asked — 'human', a brain label, a tool name. Provenance, not authorization. */\n readonly source: string\n /** The worker LABEL the request targets (already resolved by the caller). */\n readonly worker: string\n readonly message: string\n}\n\n/** The root every supervisor run of one workspace lives under. */\nexport function supervisorRunsRoot(rootDir: string): string {\n return join(resolve(rootDir), '.agent', 'supervisor')\n}\n\n/** The run directory every artifact of one supervisor run lives under. */\nexport function supervisorRunDir(rootDir: string, id: string): string {\n return join(supervisorRunsRoot(rootDir), id)\n}\n\n/**\n * Where a pre-rename writer put the same run (`<root>/.loops/supervisor/<id>`). Readers that must\n * see historical runs check {@link supervisorRunDir} first and fall back to this; nothing writes\n * here anymore.\n */\nexport function legacySupervisorRunDir(rootDir: string, id: string): string {\n return join(legacySupervisorRunsRoot(rootDir), id)\n}\n\n/**\n * The pre-rename runs root (`<root>/.loops/supervisor`). Only readers that ENUMERATE historical\n * runs need this — the per-id form is {@link legacySupervisorRunDir}. Nothing writes here.\n */\nexport function legacySupervisorRunsRoot(rootDir: string): string {\n return join(resolve(rootDir), '.loops', 'supervisor')\n}\n\n/** A worker label reduced to a safe filename stem. Empty labels get a stable fallback. */\nexport function safeWorkerFile(label: string): string {\n const safe = label.replace(/[^A-Za-z0-9._-]/g, '_')\n return safe.length > 0 ? safe : 'worker'\n}\n\n/** The directory holding every per-worker file of one run (inboxes and control-event logs). */\nexport function supervisorWorkersDir(eventDir: string): string {\n return join(eventDir, 'workers')\n}\n\n/** The durable inbox file for one worker of one run. */\nexport function workerInboxFile(rootDir: string, supervisorId: string, worker: string): string {\n return workerInboxFileFromEventDir(supervisorRunDir(rootDir, supervisorId), worker)\n}\n\n/** Same, addressed from an already-known run directory (the reader's usual entry point). */\nexport function workerInboxFileFromEventDir(eventDir: string, worker: string): string {\n return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.inbox.ndjson`)\n}\n\n/**\n * The best-effort control-event log for one worker (`workers/<label>.ndjson`) — delivery\n * bookkeeping for steers, plus whatever lifecycle events a writer chooses to append. Distinct from\n * the inbox: the inbox is the durable down-leg queue, this is the record of what happened to it.\n */\nexport function workerControlLogFile(eventDir: string, worker: string): string {\n return join(supervisorWorkersDir(eventDir), `${safeWorkerFile(worker)}.ndjson`)\n}\n\n/**\n * Durably append one steer request to a worker's inbox and log the delivery attempt.\n *\n * The inbox append is the durable act; the control-event log is best-effort bookkeeping and may\n * silently fail without voiding the steer.\n */\nexport function writeWorkerSteer(\n rootDir: string,\n supervisorId: string,\n worker: string,\n message: string,\n source = 'human',\n): { worker: string; file: string; request: WorkerSteerRequest } {\n const trimmed = message.trim()\n if (!trimmed) throw new Error('steer message is empty')\n const dir = supervisorRunDir(rootDir, supervisorId)\n mkdirSync(supervisorWorkersDir(dir), { recursive: true })\n const request: WorkerSteerRequest = {\n id: randomUUID(),\n at: new Date().toISOString(),\n source,\n worker,\n message: trimmed,\n }\n const file = workerInboxFile(rootDir, supervisorId, worker)\n appendFileSync(file, `${JSON.stringify(request)}\\n`, 'utf8')\n appendWorkerControlEvent(dir, worker, {\n kind: 'message',\n direction: 'down',\n source,\n requestId: request.id,\n message: trimmed,\n queued: true,\n delivered: false,\n })\n return { worker, file, request }\n}\n\n/** Read every valid steer request in a worker's inbox. Corrupt or partial lines are skipped. */\nexport function readWorkerSteerRequests(eventDir: string, worker: string): WorkerSteerRequest[] {\n const file = workerInboxFileFromEventDir(eventDir, worker)\n if (!existsSync(file)) return []\n const out: WorkerSteerRequest[] = []\n let raw = ''\n try {\n raw = readFileSync(file, 'utf8')\n } catch {\n return out\n }\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim()\n if (!trimmed) continue\n try {\n const parsed = JSON.parse(trimmed) as Partial<WorkerSteerRequest>\n if (isWorkerSteerRequest(parsed)) out.push(parsed)\n } catch {\n // Ignore partial or corrupt input lines; later valid lines still matter.\n }\n }\n return out\n}\n\nfunction appendWorkerControlEvent(\n eventDir: string,\n label: string,\n event: Record<string, unknown>,\n): void {\n try {\n mkdirSync(supervisorWorkersDir(eventDir), { recursive: true })\n appendFileSync(\n workerControlLogFile(eventDir, label),\n `${JSON.stringify({ at: new Date().toISOString(), label, ...event })}\\n`,\n 'utf8',\n )\n } catch {\n // Control logging is best-effort; the inbox write above is the durable part.\n }\n}\n\nfunction isWorkerSteerRequest(value: Partial<WorkerSteerRequest>): value is WorkerSteerRequest {\n return (\n typeof value.id === 'string' &&\n typeof value.at === 'string' &&\n typeof value.source === 'string' &&\n typeof value.worker === 'string' &&\n typeof value.message === 'string' &&\n value.message.trim().length > 0\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,SAAgB,mBAAmB,SAAyB;CAC1D,OAAO,KAAK,QAAQ,OAAO,GAAG,UAAU,YAAY;AACtD;;AAGA,SAAgB,iBAAiB,SAAiB,IAAoB;CACpE,OAAO,KAAK,mBAAmB,OAAO,GAAG,EAAE;AAC7C;;;;;;AAOA,SAAgB,uBAAuB,SAAiB,IAAoB;CAC1E,OAAO,KAAK,yBAAyB,OAAO,GAAG,EAAE;AACnD;;;;;AAMA,SAAgB,yBAAyB,SAAyB;CAChE,OAAO,KAAK,QAAQ,OAAO,GAAG,UAAU,YAAY;AACtD;;AAGA,SAAgB,eAAe,OAAuB;CACpD,MAAM,OAAO,MAAM,QAAQ,oBAAoB,GAAG;CAClD,OAAO,KAAK,SAAS,IAAI,OAAO;AAClC;;AAGA,SAAgB,qBAAqB,UAA0B;CAC7D,OAAO,KAAK,UAAU,SAAS;AACjC;;AAGA,SAAgB,gBAAgB,SAAiB,cAAsB,QAAwB;CAC7F,OAAO,4BAA4B,iBAAiB,SAAS,YAAY,GAAG,MAAM;AACpF;;AAGA,SAAgB,4BAA4B,UAAkB,QAAwB;CACpF,OAAO,KAAK,qBAAqB,QAAQ,GAAG,GAAG,eAAe,MAAM,EAAE,cAAc;AACtF;;;;;;AAOA,SAAgB,qBAAqB,UAAkB,QAAwB;CAC7E,OAAO,KAAK,qBAAqB,QAAQ,GAAG,GAAG,eAAe,MAAM,EAAE,QAAQ;AAChF;;;;;;;AAQA,SAAgB,iBACd,SACA,cACA,QACA,SACA,SAAS,SACsD;CAC/D,MAAM,UAAU,QAAQ,KAAK;CAC7B,IAAI,CAAC,SAAS,MAAM,IAAI,MAAM,wBAAwB;CACtD,MAAM,MAAM,iBAAiB,SAAS,YAAY;CAClD,UAAU,qBAAqB,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;CACxD,MAAM,UAA8B;EAClC,IAAI,WAAW;EACf,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;EAC3B;EACA;EACA,SAAS;CACX;CACA,MAAM,OAAO,gBAAgB,SAAS,cAAc,MAAM;CAC1D,eAAe,MAAM,GAAG,KAAK,UAAU,OAAO,EAAE,KAAK,MAAM;CAC3D,yBAAyB,KAAK,QAAQ;EACpC,MAAM;EACN,WAAW;EACX;EACA,WAAW,QAAQ;EACnB,SAAS;EACT,QAAQ;EACR,WAAW;CACb,CAAC;CACD,OAAO;EAAE;EAAQ;EAAM;CAAQ;AACjC;;AAGA,SAAgB,wBAAwB,UAAkB,QAAsC;CAC9F,MAAM,OAAO,4BAA4B,UAAU,MAAM;CACzD,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO,CAAC;CAC/B,MAAM,MAA4B,CAAC;CACnC,IAAI,MAAM;CACV,IAAI;EACF,MAAM,aAAa,MAAM,MAAM;CACjC,QAAQ;EACN,OAAO;CACT;CACA,KAAK,MAAM,QAAQ,IAAI,MAAM,IAAI,GAAG;EAClC,MAAM,UAAU,KAAK,KAAK;EAC1B,IAAI,CAAC,SAAS;EACd,IAAI;GACF,MAAM,SAAS,KAAK,MAAM,OAAO;GACjC,IAAI,qBAAqB,MAAM,GAAG,IAAI,KAAK,MAAM;EACnD,QAAQ,CAER;CACF;CACA,OAAO;AACT;AAEA,SAAS,yBACP,UACA,OACA,OACM;CACN,IAAI;EACF,UAAU,qBAAqB,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;EAC7D,eACE,qBAAqB,UAAU,KAAK,GACpC,GAAG,KAAK,UAAU;GAAE,qBAAI,IAAI,KAAK,EAAA,CAAE,YAAY;GAAG;GAAO,GAAG;EAAM,CAAC,EAAE,KACrE,MACF;CACF,QAAQ,CAER;AACF;AAEA,SAAS,qBAAqB,OAAiE;CAC7F,OACE,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,OAAO,YACpB,OAAO,MAAM,WAAW,YACxB,OAAO,MAAM,WAAW,YACxB,OAAO,MAAM,YAAY,YACzB,MAAM,QAAQ,KAAK,CAAC,CAAC,SAAS;AAElC"}
|