alphacouncil-agent 0.5.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.
Files changed (139) hide show
  1. package/.claude/agents/alphacouncil-bear_researcher.md +39 -0
  2. package/.claude/agents/alphacouncil-bull_researcher.md +40 -0
  3. package/.claude/agents/alphacouncil-earnings_deep_dive.md +49 -0
  4. package/.claude/agents/alphacouncil-forward_expectations.md +48 -0
  5. package/.claude/agents/alphacouncil-ib_event_analysis.md +40 -0
  6. package/.claude/agents/alphacouncil-insider_sec.md +39 -0
  7. package/.claude/agents/alphacouncil-macro_regime.md +31 -0
  8. package/.claude/agents/alphacouncil-market_data.md +37 -0
  9. package/.claude/agents/alphacouncil-market_narrative.md +45 -0
  10. package/.claude/agents/alphacouncil-news_industry_management.md +49 -0
  11. package/.claude/agents/alphacouncil-portfolio_manager.md +22 -0
  12. package/.claude/agents/alphacouncil-quant_factor.md +38 -0
  13. package/.claude/agents/alphacouncil-social_pulse.md +49 -0
  14. package/.claude/agents/alphacouncil-valuation_long_short.md +45 -0
  15. package/.claude-plugin/marketplace.json +30 -0
  16. package/.claude-plugin/plugin.json +28 -0
  17. package/.codex-plugin/plugin.json +32 -0
  18. package/.grok/agents/alphacouncil-bear_researcher.md +41 -0
  19. package/.grok/agents/alphacouncil-bull_researcher.md +42 -0
  20. package/.grok/agents/alphacouncil-earnings_deep_dive.md +50 -0
  21. package/.grok/agents/alphacouncil-forward_expectations.md +49 -0
  22. package/.grok/agents/alphacouncil-ib_event_analysis.md +41 -0
  23. package/.grok/agents/alphacouncil-insider_sec.md +40 -0
  24. package/.grok/agents/alphacouncil-macro_regime.md +32 -0
  25. package/.grok/agents/alphacouncil-market_data.md +38 -0
  26. package/.grok/agents/alphacouncil-market_narrative.md +46 -0
  27. package/.grok/agents/alphacouncil-news_industry_management.md +50 -0
  28. package/.grok/agents/alphacouncil-portfolio_manager.md +24 -0
  29. package/.grok/agents/alphacouncil-quant_factor.md +39 -0
  30. package/.grok/agents/alphacouncil-social_pulse.md +50 -0
  31. package/.grok/agents/alphacouncil-valuation_long_short.md +46 -0
  32. package/.grok/config.toml +4 -0
  33. package/.mcp.json +11 -0
  34. package/.opencode/agent/alphacouncil-bear_researcher.md +45 -0
  35. package/.opencode/agent/alphacouncil-bull_researcher.md +46 -0
  36. package/.opencode/agent/alphacouncil-earnings_deep_dive.md +54 -0
  37. package/.opencode/agent/alphacouncil-forward_expectations.md +53 -0
  38. package/.opencode/agent/alphacouncil-ib_event_analysis.md +45 -0
  39. package/.opencode/agent/alphacouncil-insider_sec.md +44 -0
  40. package/.opencode/agent/alphacouncil-macro_regime.md +36 -0
  41. package/.opencode/agent/alphacouncil-market_data.md +42 -0
  42. package/.opencode/agent/alphacouncil-market_narrative.md +50 -0
  43. package/.opencode/agent/alphacouncil-news_industry_management.md +54 -0
  44. package/.opencode/agent/alphacouncil-portfolio_manager.md +28 -0
  45. package/.opencode/agent/alphacouncil-quant_factor.md +43 -0
  46. package/.opencode/agent/alphacouncil-social_pulse.md +54 -0
  47. package/.opencode/agent/alphacouncil-valuation_long_short.md +50 -0
  48. package/AGENTS.md +96 -0
  49. package/CHANGELOG.md +112 -0
  50. package/CLAUDE.md +144 -0
  51. package/LICENSE +21 -0
  52. package/README.ja.md +282 -0
  53. package/README.md +361 -0
  54. package/README.zh-CN.md +282 -0
  55. package/data/industry-map.json +74 -0
  56. package/docs/attribution.md +99 -0
  57. package/docs/personas.md +59 -0
  58. package/docs/report-contract.md +110 -0
  59. package/mcp/lib/codex.mjs +152 -0
  60. package/mcp/lib/constants.mjs +139 -0
  61. package/mcp/lib/council-options.mjs +111 -0
  62. package/mcp/lib/errors.mjs +46 -0
  63. package/mcp/lib/feeds.mjs +140 -0
  64. package/mcp/lib/fsutil.mjs +69 -0
  65. package/mcp/lib/gates.mjs +208 -0
  66. package/mcp/lib/grounding.mjs +252 -0
  67. package/mcp/lib/headings.mjs +65 -0
  68. package/mcp/lib/industry.mjs +208 -0
  69. package/mcp/lib/lang.mjs +24 -0
  70. package/mcp/lib/macro.mjs +167 -0
  71. package/mcp/lib/markdown.mjs +337 -0
  72. package/mcp/lib/markets-kr-jp.mjs +180 -0
  73. package/mcp/lib/markets.mjs +261 -0
  74. package/mcp/lib/narrative.mjs +235 -0
  75. package/mcp/lib/options.mjs +216 -0
  76. package/mcp/lib/orchestrator.mjs +541 -0
  77. package/mcp/lib/output-modes.mjs +144 -0
  78. package/mcp/lib/packets.mjs +256 -0
  79. package/mcp/lib/personas/frontmatter.mjs +80 -0
  80. package/mcp/lib/personas/registry.mjs +215 -0
  81. package/mcp/lib/preflight.mjs +174 -0
  82. package/mcp/lib/prompts.mjs +151 -0
  83. package/mcp/lib/quotes.mjs +108 -0
  84. package/mcp/lib/rpc.mjs +737 -0
  85. package/mcp/lib/run-store.mjs +140 -0
  86. package/mcp/lib/screen.mjs +283 -0
  87. package/mcp/lib/sec.mjs +131 -0
  88. package/mcp/lib/social.mjs +261 -0
  89. package/mcp/lib/tables.mjs +150 -0
  90. package/mcp/lib/text.mjs +27 -0
  91. package/mcp/lib/weights.mjs +150 -0
  92. package/mcp/server.mjs +92 -0
  93. package/opencode.json +20 -0
  94. package/package.json +61 -0
  95. package/personas/_shared/_debate_base.md +46 -0
  96. package/personas/_shared/_evidence_base.md +56 -0
  97. package/personas/_shared/_master_base.md +77 -0
  98. package/personas/analysts/earnings_deep_dive.md +118 -0
  99. package/personas/analysts/forward_expectations.md +115 -0
  100. package/personas/analysts/ib_event_analysis.md +100 -0
  101. package/personas/analysts/insider_sec.md +98 -0
  102. package/personas/analysts/macro_regime.md +66 -0
  103. package/personas/analysts/market_data.md +94 -0
  104. package/personas/analysts/market_narrative.md +94 -0
  105. package/personas/analysts/news_industry_management.md +117 -0
  106. package/personas/analysts/quant_factor.md +97 -0
  107. package/personas/analysts/social_pulse.md +102 -0
  108. package/personas/analysts/valuation_long_short.md +109 -0
  109. package/personas/debate/bear_researcher.md +96 -0
  110. package/personas/debate/bull_researcher.md +98 -0
  111. package/personas/debate/portfolio_manager.md +64 -0
  112. package/personas/masters/masters-adversarial/burry.md +137 -0
  113. package/personas/masters/masters-adversarial/dalio.md +139 -0
  114. package/personas/masters/masters-adversarial/druckenmiller.md +144 -0
  115. package/personas/masters/masters-adversarial/short_seller.md +145 -0
  116. package/personas/masters/masters-adversarial/soros.md +137 -0
  117. package/personas/masters/masters-modern/aschenbrenner.md +147 -0
  118. package/personas/masters/masters-options/natenberg.md +168 -0
  119. package/personas/masters/masters-options/sinclair.md +166 -0
  120. package/personas/masters/masters-options/taleb.md +163 -0
  121. package/personas/masters/masters-quant/asness.md +142 -0
  122. package/personas/masters/masters-quant/simons.md +143 -0
  123. package/personas/masters/masters-quant/thorp.md +143 -0
  124. package/personas/masters/masters-value/buffett.md +123 -0
  125. package/personas/masters/masters-value/duan_yongping.md +142 -0
  126. package/personas/masters/masters-value/li_lu.md +146 -0
  127. package/personas/masters/masters-value/munger.md +117 -0
  128. package/personas/masters/masters-value-classic/fisher.md +140 -0
  129. package/personas/masters/masters-value-classic/graham.md +137 -0
  130. package/personas/masters/masters-value-classic/klarman.md +138 -0
  131. package/personas/masters/masters-value-classic/lynch.md +133 -0
  132. package/personas/masters/masters-value-classic/marks.md +145 -0
  133. package/personas/verifiers/rederivation.md +97 -0
  134. package/personas/verifiers/refuter.md +99 -0
  135. package/personas/verifiers/source_fidelity.md +91 -0
  136. package/skills/agent-skills-governance/SKILL.md +42 -0
  137. package/skills/alphacouncil-agent/SKILL.md +259 -0
  138. package/skills/investment-banking/SKILL.md +70 -0
  139. package/skills/public-equity-investing/SKILL.md +74 -0
@@ -0,0 +1,541 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { DEBATE_ROLES, DEFAULT_TASKS, LIMITS, OUTPUT_MODES } from "./constants.mjs";
4
+ import { invalidParams } from "./errors.mjs";
5
+ import { readJson, writeJson } from "./fsutil.mjs";
6
+ import { registry } from "./personas/registry.mjs";
7
+ import { isChineseLanguage, resolveLanguage } from "./lang.mjs";
8
+ import { cleanLog } from "./text.mjs";
9
+ import { completenessStatus, verificationStatus } from "./gates.mjs";
10
+ import { agentState, appendEvent, artifactPaths, existingDebate, runPath, runId, safeSymbol, saveRun, taskState, today, updateAgent, updateTask, writeSourceManifest, writeStatus } from "./run-store.mjs";
11
+ import { writeAllAgentsMarkdown, writeAnalystMarkdownFiles, writeArtifactIndex, writeFinalArtifacts } from "./markdown.mjs";
12
+ import { debateFromCodex, dryDebate, dryPacket, extractJson, managerFallback, mergeDebateRounds, normalizeDebate, normalizeMasterOpinion, normalizePacket, rawRecordText } from "./packets.mjs";
13
+ import { mapLimit, runCodex } from "./codex.mjs";
14
+ import { debatePrompt, masterPrompt, selectedMasters, taskPrompt } from "./prompts.mjs";
15
+ import { resolveSeatWeights } from "./weights.mjs";
16
+
17
+ export function visibleRun(args) {
18
+ const symbol = safeSymbol(args.symbol);
19
+ const asOfDate = args.as_of || today();
20
+ const id = args.run_id || runId(symbol);
21
+ const tasks = Array.isArray(args.tasks) && args.tasks.length ? args.tasks : DEFAULT_TASKS;
22
+ const language = resolveLanguage(args);
23
+ const dir = runPath(id);
24
+ mkdirSync(dir, { recursive: true });
25
+ const startedAt = new Date().toISOString();
26
+ const run = {
27
+ run_id: id,
28
+ symbol,
29
+ as_of: asOfDate,
30
+ language,
31
+ dry_run: false,
32
+ execution_mode: "visible_host_threads",
33
+ visibility_required: true,
34
+ started_at: startedAt,
35
+ updated_at: startedAt,
36
+ completed_at: null,
37
+ status: "planned",
38
+ phase: "visible_planned",
39
+ tasks,
40
+ task_status: Object.fromEntries(tasks.map((task) => [task, { task, status: "pending" }])),
41
+ agent_status: Object.fromEntries(DEBATE_ROLES.map((role) => [role, { role, status: "pending" }])),
42
+ packets: [],
43
+ // Masters are an optional judgment layer. They are deliberately NOT part of the
44
+ // completeness gate: turning the bench on must not be able to mark a run incomplete.
45
+ masters_roster: typeof args.masters_roster === "string" ? args.masters_roster : undefined,
46
+ masters: Array.isArray(args.masters) ? args.masters : undefined,
47
+ master_opinions: [],
48
+ // Verifier outcomes, keyed to the seat that cited the claim. These drive the
49
+ // down-weighting in weights.mjs.
50
+ verifier_verdicts: [],
51
+ // Deterministic facts injected into every analyst prompt, so search explains and
52
+ // challenges established numbers rather than re-deriving them from nothing.
53
+ grounding: args.grounding && typeof args.grounding === "object" ? args.grounding : null,
54
+ seat_weight_overrides: (args.seat_weights && typeof args.seat_weights === "object") ? args.seat_weights : {},
55
+ };
56
+ writeStatus(run);
57
+ appendEvent(run, "visible_run_planned", { tasks, masters: selectedMasters(run) });
58
+ writeJson(join(dir, "evidence.json"), run);
59
+ writeSourceManifest(run);
60
+ writeAllAgentsMarkdown(run);
61
+ return run;
62
+ }
63
+
64
+ export function visibleAgentSpecs(run, userPrompt = "") {
65
+ const evidence_agents = run.tasks.map((task) => ({
66
+ role: task,
67
+ title: isChineseLanguage(run.language) ? `AlphaCouncil Agent ${run.symbol} ${task} 证据子代理` : `AlphaCouncil Agent ${run.symbol} ${task} evidence subagent`,
68
+ prompt: taskPrompt(task, run.symbol, run.as_of, userPrompt, run.language, run.grounding),
69
+ output_contract: isChineseLanguage(run.language) ? "只返回一个 JSON evidence packet。" : `Return one JSON evidence packet with reader-facing fields in ${run.language}.`,
70
+ }));
71
+ const debate_agents = DEBATE_ROLES.map((role) => ({
72
+ role,
73
+ title: `AlphaCouncil Agent ${run.symbol} ${role}`,
74
+ prompt_template: [
75
+ debatePrompt(role, run),
76
+ "",
77
+ isChineseLanguage(run.language) ? "主线程必须先粘贴已完成的 Evidence JSON,再运行这个可见代理。" : "The main thread must paste the completed Evidence JSON before running this visible agent.",
78
+ role === "bear_researcher" ? (isChineseLanguage(run.language) ? "主线程还必须粘贴 Bull argument JSON。" : "The main thread must also paste Bull argument JSON.") : "",
79
+ role === "portfolio_manager" ? (isChineseLanguage(run.language) ? "主线程还必须粘贴 Bull 和 Bear argument JSON。" : "The main thread must also paste Bull and Bear argument JSON.") : "",
80
+ ].filter(Boolean).join("\n"),
81
+ output_contract: isChineseLanguage(run.language) ? "只返回一个 JSON debate packet。" : `Return one JSON debate packet with reader-facing fields in ${run.language}.`,
82
+ }));
83
+ const master_agents = selectedMasters(run).map((id) => ({
84
+ role: id,
85
+ title: `AlphaCouncil Agent ${run.symbol} ${id}`,
86
+ prompt_template: [
87
+ masterPrompt(id, run),
88
+ "",
89
+ isChineseLanguage(run.language)
90
+ ? "主线程必须先粘贴已完成的 Evidence JSON,再运行这个大师议席;大师在证据之后、辩论之前运行。"
91
+ : "The main thread must paste the completed Evidence JSON first. Masters run after the evidence stage and before the debate.",
92
+ ].filter(Boolean).join("\n"),
93
+ output_contract: isChineseLanguage(run.language)
94
+ ? "只返回一个 JSON master opinion。"
95
+ : `Return one JSON master opinion with reader-facing fields in ${run.language}.`,
96
+ }));
97
+ return { evidence_agents, master_agents, debate_agents };
98
+ }
99
+
100
+ export function recordMasterOpinion(args) {
101
+ const run = readJson(join(runPath(args.run_id), "evidence.json"));
102
+ if (run.execution_mode !== "visible_host_threads") {
103
+ throw invalidParams("record_master_opinion requires a run created by plan_visible_run.");
104
+ }
105
+ const allowed = selectedMasters(run);
106
+ if (!allowed.includes(args.master)) {
107
+ throw invalidParams(`master ${args.master} was not selected for this run. Selected: ${allowed.join(", ") || "none"}`);
108
+ }
109
+ const dir = runPath(run.run_id);
110
+ const opinion = normalizeMasterOpinion(
111
+ { ...(args.packet || {}), thread_id: args.thread_id },
112
+ args.master,
113
+ run,
114
+ rawRecordText(args.packet),
115
+ );
116
+ const byId = new Map((run.master_opinions || []).map((item) => [item.master, item]));
117
+ byId.set(args.master, opinion);
118
+ run.master_opinions = allowed.map((id) => byId.get(id)).filter(Boolean);
119
+ writeJson(join(dir, `${args.master}.json`), opinion);
120
+ saveRun(run);
121
+ writeJson(join(dir, "evidence.json"), run);
122
+ appendEvent(run, "master_opinion_recorded", { master: args.master, stance: opinion.stance });
123
+ return { run, opinion, recorded: run.master_opinions.length, expected: allowed.length };
124
+ }
125
+
126
+ export function visibleStatusAfterPacket(run) {
127
+ if (agentState(run, "portfolio_manager").status === "completed") {
128
+ let finished;
129
+ if (completenessStatus(run).completeness === "incomplete") finished = "incomplete";
130
+ else if (verificationStatus(run).verification === "needs_verification") finished = "needs_verification";
131
+ else if (run.report_quality?.status === "needs_revision") finished = "needs_revision";
132
+ else finished = "complete";
133
+ return { status: finished, phase: finished, completed_at: run.completed_at || new Date().toISOString() };
134
+ }
135
+ if (DEBATE_ROLES.some((role) => agentState(run, role).status === "completed")) {
136
+ return { status: "running", phase: "visible_debate", completed_at: null };
137
+ }
138
+ return { status: "running", phase: "visible_evidence", completed_at: null };
139
+ }
140
+
141
+ export function recordVisiblePacket(args) {
142
+ const run = readJson(join(runPath(args.run_id), "evidence.json"));
143
+ if (run.execution_mode !== "visible_host_threads") {
144
+ throw invalidParams("record_visible_packet requires a run created by plan_visible_run.");
145
+ }
146
+ const task = args.task;
147
+ if (!run.tasks.includes(task)) throw invalidParams(`Unknown task for this run: ${task}`);
148
+ const dir = runPath(run.run_id);
149
+ const packet = normalizePacket({
150
+ ...(args.packet || {}),
151
+ thread_id: args.thread_id,
152
+ thread_title: args.thread_title,
153
+ execution_mode: "visible_host_threads",
154
+ }, task, run.symbol, run.as_of, rawRecordText(args.packet));
155
+ const byTask = new Map(run.packets.map((item) => [item.task, item]));
156
+ byTask.set(task, packet);
157
+ run.packets = run.tasks.map((item) => byTask.get(item)).filter(Boolean);
158
+ Object.assign(run, visibleStatusAfterPacket(run));
159
+ writeJson(join(dir, `${task}.json`), packet);
160
+ saveRun(run);
161
+ updateTask(run, task, "completed", {
162
+ completed_at: new Date().toISOString(),
163
+ thread_id: args.thread_id,
164
+ thread_title: args.thread_title,
165
+ output: join(dir, `${task}.json`),
166
+ });
167
+ writeJson(join(dir, "evidence.json"), run);
168
+ writeAnalystMarkdownFiles(run, existingDebate(dir));
169
+ writeArtifactIndex(run, existingDebate(dir));
170
+ writeAllAgentsMarkdown(run, existingDebate(dir));
171
+ return run;
172
+ }
173
+
174
+ export function recordVisibleDecision(args) {
175
+ const run = readJson(join(runPath(args.run_id), "evidence.json"));
176
+ if (run.execution_mode !== "visible_host_threads") {
177
+ throw invalidParams("record_visible_decision requires a run created by plan_visible_run.");
178
+ }
179
+ const role = args.role;
180
+ if (!DEBATE_ROLES.includes(role)) throw invalidParams(`Unknown decision role: ${role}`);
181
+ const dir = runPath(run.run_id);
182
+ const packet = normalizeDebate({
183
+ ...(args.packet || {}),
184
+ thread_id: args.thread_id,
185
+ thread_title: args.thread_title,
186
+ execution_mode: "visible_host_threads",
187
+ }, role, run, rawRecordText(args.packet));
188
+ const file = role === "portfolio_manager" ? "manager_synthesis.json" : `${role}.json`;
189
+ writeJson(join(dir, file), packet);
190
+ // Mark the agent completed BEFORE evaluating the gates. The completeness gate now
191
+ // counts portfolio_manager, so reading it first would report every finished run as
192
+ // incomplete -- the PM would never be recorded by the time it was checked.
193
+ updateAgent(run, role, "completed", {
194
+ completed_at: new Date().toISOString(),
195
+ thread_id: args.thread_id,
196
+ thread_title: args.thread_title,
197
+ output: join(dir, file),
198
+ });
199
+ let finalArtifacts = {};
200
+ if (role === "portfolio_manager") {
201
+ const gate = verificationStatus(run);
202
+ const completeness = completenessStatus(run);
203
+ writeJson(join(dir, "decision.json"), packet);
204
+ if (completeness.completeness === "incomplete") {
205
+ run.status = "incomplete";
206
+ run.phase = "incomplete";
207
+ appendEvent(run, "incomplete", {
208
+ missing_evidence: completeness.missing_evidence,
209
+ missing_debate: completeness.missing_debate,
210
+ });
211
+ } else if (gate.verification === "needs_verification") {
212
+ run.status = "needs_verification";
213
+ run.phase = "needs_verification";
214
+ appendEvent(run, "needs_verification", { missing: gate.missing_claim_source_ids.length });
215
+ } else {
216
+ run.status = "complete";
217
+ run.phase = "complete";
218
+ }
219
+ run.completed_at = new Date().toISOString();
220
+ } else {
221
+ run.status = "running";
222
+ run.phase = "visible_debate";
223
+ }
224
+ saveRun(run);
225
+ writeJson(join(dir, "evidence.json"), run);
226
+ if (role === "portfolio_manager") {
227
+ finalArtifacts = writeFinalArtifacts(run, existingDebate(dir));
228
+ writeJson(join(dir, "evidence.json"), run);
229
+ if (run.status === "complete") appendEvent(run, "run_complete", { decision: packet.rating, winner: packet.winner });
230
+ } else {
231
+ writeAnalystMarkdownFiles(run, existingDebate(dir));
232
+ writeArtifactIndex(run, existingDebate(dir));
233
+ }
234
+ writeStatus(run);
235
+ writeAllAgentsMarkdown(run, existingDebate(dir));
236
+ return { run, decision: packet, ...finalArtifacts };
237
+ }
238
+
239
+ export function isDryRun(args = {}) {
240
+ return args.dry_run === true;
241
+ }
242
+
243
+ export async function collectEvidence(args) {
244
+ if (args.visibility_required) {
245
+ throw invalidParams("visibility_required=true cannot be satisfied by headless MCP. Use host-level multi_agent or codex_app threads first, then record_visible_packet/record_visible_decision.");
246
+ }
247
+ const symbol = safeSymbol(args.symbol);
248
+ const asOfDate = args.as_of || today();
249
+ const id = args.run_id || runId(symbol);
250
+ const tasks = Array.isArray(args.tasks) && args.tasks.length ? args.tasks : DEFAULT_TASKS;
251
+ const dryRun = isDryRun(args);
252
+ const language = resolveLanguage(args);
253
+ const timeoutMs = Number.isFinite(args.timeout_ms) ? args.timeout_ms : LIMITS.CODEX_TIMEOUT_MS;
254
+ const maxConcurrency = Math.max(LIMITS.CONCURRENCY_MIN, Math.min(LIMITS.CONCURRENCY_MAX, Number(args.max_concurrency || LIMITS.CONCURRENCY_DEFAULT)));
255
+ const dir = runPath(id);
256
+ mkdirSync(dir, { recursive: true });
257
+
258
+ const startedAt = new Date().toISOString();
259
+ const run = {
260
+ run_id: id,
261
+ symbol,
262
+ as_of: asOfDate,
263
+ language,
264
+ dry_run: dryRun,
265
+ execution_mode: dryRun ? "dry_run" : "background_codex_exec",
266
+ visibility_required: false,
267
+ started_at: startedAt,
268
+ updated_at: startedAt,
269
+ completed_at: null,
270
+ status: "running",
271
+ phase: "evidence",
272
+ tasks,
273
+ task_status: Object.fromEntries(tasks.map((task) => [task, { task, status: "pending" }])),
274
+ agent_status: {},
275
+ packets: [],
276
+ };
277
+ const packetsByTask = new Map();
278
+ writeStatus(run);
279
+ appendEvent(run, "run_started", { tasks });
280
+ writeJson(join(dir, "evidence.json"), run);
281
+ writeSourceManifest(run);
282
+ writeAllAgentsMarkdown(run);
283
+
284
+ const commitPacket = (packet) => {
285
+ packetsByTask.set(packet.task, packet);
286
+ run.packets = tasks.map((task) => packetsByTask.get(task)).filter(Boolean);
287
+ writeJson(join(dir, `${packet.task}.json`), packet);
288
+ writeJson(join(dir, "evidence.json"), run);
289
+ writeSourceManifest(run);
290
+ writeAnalystMarkdownFiles(run, existingDebate(dir));
291
+ writeArtifactIndex(run, existingDebate(dir));
292
+ writeAllAgentsMarkdown(run);
293
+ };
294
+
295
+ await mapLimit(tasks, maxConcurrency, async (task) => {
296
+ const prompt = taskPrompt(task, symbol, asOfDate, args.prompt || "", language);
297
+ updateTask(run, task, "running", { started_at: new Date().toISOString() });
298
+ if (dryRun) {
299
+ const packet = dryPacket(task, symbol, asOfDate, prompt, language);
300
+ commitPacket(packet);
301
+ updateTask(run, task, "completed", { completed_at: new Date().toISOString(), output: join(dir, `${task}.json`) });
302
+ return packet;
303
+ }
304
+ const result = await runCodex(prompt, timeoutMs, ({ pid, output }) => {
305
+ updateTask(run, task, "running", { pid, output });
306
+ }, ({ pid, output, elapsed_ms }) => {
307
+ updateTask(run, task, "running", { pid, output });
308
+ appendEvent(run, "task_heartbeat", { task, pid, output, elapsed_ms });
309
+ });
310
+ let packet;
311
+ if (!result.ok) {
312
+ const failure = cleanLog(result.stderr || result.stdout || `exit code ${result.code}`);
313
+ packet = normalizePacket({
314
+ summary: `Subagent ${task} failed or timed out.`,
315
+ claims: [{ claim: "Subagent failure", evidence: failure, confidence: "low", source_ids: [] }],
316
+ open_questions: ["Retry this packet or lower concurrency."],
317
+ confidence: "low",
318
+ }, task, symbol, asOfDate, cleanLog(result.text || result.stderr || result.stdout));
319
+ commitPacket(packet);
320
+ updateTask(run, task, result.timedOut ? "timed_out" : "failed", {
321
+ completed_at: new Date().toISOString(),
322
+ output: join(dir, `${task}.json`),
323
+ error: result.timedOut ? "timeout" : `exit code ${result.code}`,
324
+ });
325
+ return packet;
326
+ }
327
+ try {
328
+ packet = normalizePacket(extractJson(result.text), task, symbol, asOfDate, result.text);
329
+ commitPacket(packet);
330
+ updateTask(run, task, "completed", { completed_at: new Date().toISOString(), output: join(dir, `${task}.json`) });
331
+ return packet;
332
+ } catch (error) {
333
+ const raw = cleanLog(result.text);
334
+ packet = normalizePacket({
335
+ summary: `Subagent ${task} returned non-JSON output.`,
336
+ claims: [{ claim: "Output was not parseable JSON", evidence: String(error.message || error), confidence: "low", source_ids: [] }],
337
+ open_questions: ["Inspect raw_text and rerun with a stricter prompt."],
338
+ confidence: "low",
339
+ }, task, symbol, asOfDate, raw);
340
+ commitPacket(packet);
341
+ updateTask(run, task, "failed", {
342
+ completed_at: new Date().toISOString(),
343
+ output: join(dir, `${task}.json`),
344
+ error: "parse_failed",
345
+ });
346
+ return packet;
347
+ }
348
+ });
349
+
350
+ run.completed_at = new Date().toISOString();
351
+ run.phase = "evidence_complete";
352
+ run.status = tasks.every((task) => taskState(run, task).status === "completed") ? "evidence_complete" : "partial";
353
+ writeJson(join(dir, "evidence.json"), run);
354
+ writeSourceManifest(run);
355
+ writeStatus(run);
356
+ appendEvent(run, "evidence_complete", { completed: run.packets.length, total: tasks.length });
357
+ writeAllAgentsMarkdown(run);
358
+ return run;
359
+ }
360
+
361
+ export async function runDebateRole(run, role, context, timeoutMs) {
362
+ const prompt = debatePrompt(role, run, context);
363
+ updateAgent(run, role, "running", { started_at: new Date().toISOString(), round: context.round });
364
+ const result = await runCodex(prompt, timeoutMs, ({ pid, output }) => {
365
+ updateAgent(run, role, "running", { pid, output, round: context.round });
366
+ }, ({ pid, output, elapsed_ms }) => {
367
+ updateAgent(run, role, "running", { pid, output, round: context.round });
368
+ appendEvent(run, "agent_heartbeat", { role, round: context.round, pid, output, elapsed_ms });
369
+ });
370
+ const packet = debateFromCodex(result, role, run, prompt);
371
+ return { packet, result };
372
+ }
373
+
374
+ export async function synthesizeDecision(run, args) {
375
+ const dir = runPath(run.run_id);
376
+ const timeoutMs = Number.isFinite(args.synthesis_timeout_ms) ? args.synthesis_timeout_ms : Number(args.timeout_ms || LIMITS.CODEX_TIMEOUT_MS);
377
+ const outputMode = OUTPUT_MODES.includes(args.output_mode) ? args.output_mode : "public_equity";
378
+ run.phase = "debate";
379
+ run.status = "running";
380
+ run.completed_at = null;
381
+ writeStatus(run);
382
+ appendEvent(run, "debate_started", { output_mode: outputMode });
383
+ if (run.dry_run || args.synthesis === false) {
384
+ updateAgent(run, "bull_researcher", "running", { started_at: new Date().toISOString() });
385
+ const bull = dryDebate("bull_researcher", run, debatePrompt("bull_researcher", run));
386
+ updateAgent(run, "bull_researcher", "completed", { completed_at: new Date().toISOString(), output: join(dir, "bull_researcher.json") });
387
+ updateAgent(run, "bear_researcher", "running", { started_at: new Date().toISOString() });
388
+ const bear = dryDebate("bear_researcher", run, debatePrompt("bear_researcher", run, { bull }));
389
+ updateAgent(run, "bear_researcher", "completed", { completed_at: new Date().toISOString(), output: join(dir, "bear_researcher.json") });
390
+ updateAgent(run, "portfolio_manager", "running", { started_at: new Date().toISOString() });
391
+ const fallback = managerFallback(run, args.prompt || "");
392
+ updateAgent(run, "portfolio_manager", "completed", { completed_at: new Date().toISOString(), output: join(dir, "manager_synthesis.json") });
393
+ writeJson(join(dir, "bull_researcher.json"), bull);
394
+ writeJson(join(dir, "bear_researcher.json"), bear);
395
+ writeJson(join(dir, "manager_synthesis.json"), fallback);
396
+ writeJson(join(dir, "decision.json"), fallback);
397
+ const dryGate = verificationStatus(run);
398
+ const dryCompleteness = completenessStatus(run);
399
+ run.completed_at = new Date().toISOString();
400
+ if (dryCompleteness.completeness === "incomplete") {
401
+ run.phase = "incomplete";
402
+ run.status = "incomplete";
403
+ appendEvent(run, "incomplete", { missing_evidence: dryCompleteness.missing_evidence, missing_debate: dryCompleteness.missing_debate });
404
+ } else if (dryGate.verification === "needs_verification") {
405
+ run.phase = "needs_verification";
406
+ run.status = "needs_verification";
407
+ appendEvent(run, "needs_verification", { missing: dryGate.missing_claim_source_ids.length });
408
+ } else {
409
+ run.phase = "complete";
410
+ run.status = "complete";
411
+ }
412
+ const finalArtifacts = writeFinalArtifacts(run, { bull, bear, manager: fallback });
413
+ writeJson(join(dir, "evidence.json"), run);
414
+ writeStatus(run);
415
+ if (run.status === "complete") appendEvent(run, "run_complete", { decision: fallback.rating, winner: fallback.winner });
416
+ writeAllAgentsMarkdown(run, { bull, bear, manager: fallback });
417
+ return { bull, bear, manager: fallback, ...finalArtifacts };
418
+ }
419
+
420
+ // Three-round debate: R1 cases, R2 cross-rebuttal, R3 Q&A.
421
+ appendEvent(run, "debate_round", { round: 1 });
422
+ const bullR1 = await runDebateRole(run, "bull_researcher", { round: 1, brief: "long" }, timeoutMs);
423
+ const bearR1 = await runDebateRole(run, "bear_researcher", { round: 1, brief: "short", bull: bullR1.packet }, timeoutMs);
424
+
425
+ appendEvent(run, "debate_round", { round: 2 });
426
+ const bullR2 = await runDebateRole(run, "bull_researcher", { round: 2, otherCaseR1: bearR1.packet }, timeoutMs);
427
+ const bearR2 = await runDebateRole(run, "bear_researcher", { round: 2, otherCaseR1: bullR1.packet }, timeoutMs);
428
+
429
+ appendEvent(run, "debate_round", { round: 3 });
430
+ const bullR3 = await runDebateRole(run, "bull_researcher", { round: 3, otherCaseR1: bearR2.packet, questionsForYou: bearR2.packet.questions }, timeoutMs);
431
+ const bearR3 = await runDebateRole(run, "bear_researcher", { round: 3, otherCaseR1: bullR2.packet, questionsForYou: bullR2.packet.questions }, timeoutMs);
432
+
433
+ const bull = mergeDebateRounds([bullR1.packet, bullR2.packet, bullR3.packet]);
434
+ const bear = mergeDebateRounds([bearR1.packet, bearR2.packet, bearR3.packet]);
435
+ const bullOk = [bullR1, bullR2, bullR3].every((step) => step.result.ok) && bull.verdict !== "PARSE_FAILED";
436
+ const bearOk = [bearR1, bearR2, bearR3].every((step) => step.result.ok) && bear.verdict !== "PARSE_FAILED";
437
+ const lastBull = bullR3.result;
438
+ const lastBear = bearR3.result;
439
+
440
+ writeJson(join(dir, "bull_researcher.json"), bull);
441
+ updateAgent(run, "bull_researcher", bullOk ? "completed" : "failed", {
442
+ completed_at: new Date().toISOString(),
443
+ output: join(dir, "bull_researcher.json"),
444
+ error: bullOk ? undefined : (lastBull.timedOut ? "timeout" : `exit code ${lastBull.code}`),
445
+ });
446
+ writeJson(join(dir, "bear_researcher.json"), bear);
447
+ updateAgent(run, "bear_researcher", bearOk ? "completed" : "failed", {
448
+ completed_at: new Date().toISOString(),
449
+ output: join(dir, "bear_researcher.json"),
450
+ error: bearOk ? undefined : (lastBear.timedOut ? "timeout" : `exit code ${lastBear.code}`),
451
+ });
452
+ writeAllAgentsMarkdown(run, { bull, bear });
453
+
454
+ const managerPrompt = debatePrompt("portfolio_manager", run, { bull, bear, outputMode });
455
+ updateAgent(run, "portfolio_manager", "running", { started_at: new Date().toISOString() });
456
+ const managerResult = await runCodex(managerPrompt, timeoutMs, ({ pid, output }) => {
457
+ updateAgent(run, "portfolio_manager", "running", { pid, output });
458
+ }, ({ pid, output, elapsed_ms }) => {
459
+ updateAgent(run, "portfolio_manager", "running", { pid, output });
460
+ appendEvent(run, "agent_heartbeat", { role: "portfolio_manager", pid, output, elapsed_ms });
461
+ });
462
+ const manager = managerResult.ok
463
+ ? debateFromCodex(managerResult, "portfolio_manager", run, managerPrompt)
464
+ : managerFallback(run, args.prompt || "");
465
+ const gate = verificationStatus(run);
466
+ writeJson(join(dir, "manager_synthesis.json"), manager);
467
+ writeJson(join(dir, "decision.json"), manager);
468
+ updateAgent(run, "portfolio_manager", managerResult.ok && manager.verdict !== "PARSE_FAILED" ? "completed" : "failed", {
469
+ completed_at: new Date().toISOString(),
470
+ output: join(dir, "manager_synthesis.json"),
471
+ error: managerResult.ok ? undefined : (managerResult.timedOut ? "timeout" : `exit code ${managerResult.code}`),
472
+ });
473
+ const completeness = completenessStatus(run);
474
+ run.completed_at = new Date().toISOString();
475
+ if (completeness.completeness === "incomplete") {
476
+ run.phase = "incomplete";
477
+ run.status = "incomplete";
478
+ appendEvent(run, "incomplete", { missing_evidence: completeness.missing_evidence, missing_debate: completeness.missing_debate });
479
+ } else if (gate.verification === "needs_verification") {
480
+ run.phase = "needs_verification";
481
+ run.status = "needs_verification";
482
+ appendEvent(run, "needs_verification", { missing: gate.missing_claim_source_ids.length });
483
+ } else {
484
+ run.phase = "complete";
485
+ run.status = "complete";
486
+ }
487
+ const finalArtifacts = writeFinalArtifacts(run, { bull, bear, manager });
488
+ writeJson(join(dir, "evidence.json"), run);
489
+ writeStatus(run);
490
+ if (run.status === "complete") appendEvent(run, "run_complete", { decision: manager.rating, winner: manager.winner });
491
+ writeAllAgentsMarkdown(run, { bull, bear, manager });
492
+ return { bull, bear, manager, ...finalArtifacts };
493
+ }
494
+
495
+ export async function analyzeSymbol(args) {
496
+ const run = await collectEvidence(args);
497
+ const debate = await synthesizeDecision(run, args);
498
+ return {
499
+ run,
500
+ debate,
501
+ decision: debate.manager,
502
+ final_report_markdown: debate.final_report_markdown,
503
+ user_response_markdown: debate.user_response_markdown,
504
+ report_quality: debate.report_quality,
505
+ artifacts: debate.artifacts || artifactPaths(run),
506
+ };
507
+ }
508
+
509
+ export function recordVerifierVerdict(args) {
510
+ const run = readJson(join(runPath(args.run_id), "evidence.json"));
511
+ if (run.execution_mode !== "visible_host_threads") {
512
+ throw invalidParams("record_verifier_verdict requires a run created by plan_visible_run.");
513
+ }
514
+ const verifier = registry().get(args.verifier);
515
+ if (!verifier || verifier.kind !== "verifier") {
516
+ throw invalidParams(`unknown verifier: ${args.verifier}`);
517
+ }
518
+ if (!verifier.verdict_values.includes(args.verdict)) {
519
+ throw invalidParams(`verdict must be one of ${verifier.verdict_values.join(", ")} for ${args.verifier}, got ${JSON.stringify(args.verdict)}`);
520
+ }
521
+ run.verifier_verdicts = [
522
+ ...(run.verifier_verdicts || []),
523
+ {
524
+ verifier: args.verifier,
525
+ seat: args.seat,
526
+ verdict: args.verdict,
527
+ claim: typeof args.claim === "string" ? args.claim : "",
528
+ note: typeof args.note === "string" ? args.note : "",
529
+ at: new Date().toISOString(),
530
+ },
531
+ ];
532
+ saveRun(run);
533
+ writeJson(join(runPath(run.run_id), "evidence.json"), run);
534
+ appendEvent(run, "verifier_verdict", { verifier: args.verifier, seat: args.seat, verdict: args.verdict });
535
+ return { run_id: run.run_id, recorded: run.verifier_verdicts.length, weights: resolveSeatWeights(run, run.seat_weight_overrides) };
536
+ }
537
+
538
+ /** Current weighting for a run, for the PM prompt and for the report. */
539
+ export function seatWeights(run) {
540
+ return resolveSeatWeights(run, run.seat_weight_overrides || {});
541
+ }