@alexkroman1/aai-cli 6.11.0 → 8.0.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 +1 -0
- package/dist/{_agent-DpH2pBJd.mjs → _agent-BTwZJu4P.mjs} +2 -2
- package/dist/{_bundler-C2BDwU7r.mjs → _bundler-CDuYl5Gb.mjs} +2 -2
- package/dist/_dev-env.d.ts +22 -2
- package/dist/{_dev-server-LLLWnFBs.mjs → _dev-server-Me06wgeF.mjs} +38 -47
- package/dist/_dev-server.d.ts +0 -17
- package/dist/{_init-C57bWAQ8.mjs → _init-BhEfQ6Yi.mjs} +3 -2
- package/dist/{_preflight-BtfaYtbE.mjs → _preflight-Co1G9Jww.mjs} +1 -1
- package/dist/_preflight.d.ts +1 -1
- package/dist/_resource-commands.d.ts +23 -0
- package/dist/{_slug-api-BxrsJXVI.mjs → _slug-api-z-USxPtL.mjs} +16 -12
- package/dist/_slug-api.d.ts +5 -2
- package/dist/{_studio-U3jhKlA3.mjs → _studio-DR5yzZ1T.mjs} +2 -1
- package/dist/{_templates-Dxb_P2Wz.mjs → _templates-4WcKOjS5.mjs} +5 -6
- package/dist/_templates.d.ts +3 -4
- package/dist/_workflow-scan.d.ts +108 -0
- package/dist/{build-Dfza2pRI.mjs → build-BBxf8vom.mjs} +6 -3
- package/dist/cli.mjs +346 -349
- package/dist/{client-bundler-BEIqgOtd.mjs → client-bundler-CWnG42cU.mjs} +2 -1
- package/dist/client-bundler.mjs +1 -1
- package/dist/{delete-vbpjGzqs.mjs → delete-Dl_LSfvI.mjs} +3 -3
- package/dist/{deploy-B8lubiRT.mjs → deploy-DRC8jWr8.mjs} +5 -5
- package/dist/{dev-Uq5ujP8a.mjs → dev-BvcctCn2.mjs} +1 -1
- package/dist/eval-DfQ8cm5p.mjs +56 -0
- package/dist/eval.d.ts +43 -0
- package/dist/{init-l6xfU4xX.mjs → init-CUo87gcI.mjs} +3 -3
- package/dist/{login-C71-qz8F.mjs → login-BBuM1sxH.mjs} +3 -4
- package/dist/{logs-BWVGSjLR.mjs → logs-CQ2T46dr.mjs} +2 -2
- package/dist/project-config.mjs +1 -1
- package/dist/scaffold/CLAUDE.md +442 -80
- package/dist/scaffold/package.json +6 -3
- package/dist/scaffold/server.mjs +11 -4
- package/dist/scaffold/tsconfig.json +0 -1
- package/dist/{secret-BuMuFR4B.mjs → secret-Ct34kdgU.mjs} +2 -2
- package/dist/{storage-DzRZ-eCw.mjs → storage-i_79ZwAR.mjs} +2 -2
- package/dist/{studio-BCNUpDgP.mjs → studio-CzTzPqe1.mjs} +4 -4
- package/dist/templates/briefing-desk/agent.test.ts +350 -0
- package/dist/templates/briefing-desk/agent.ts +30 -0
- package/dist/templates/briefing-desk/shared.ts +200 -0
- package/dist/templates/briefing-desk/system-prompt.md +39 -0
- package/dist/templates/briefing-desk/tools/briefing_so_far.ts +35 -0
- package/dist/templates/briefing-desk/tools/research_topic.ts +88 -0
- package/dist/templates/briefing-desk/tools/verify_claim.ts +77 -0
- package/dist/templates/call-audit/agent.eval.test.ts +474 -0
- package/dist/templates/call-audit/agent.test.ts +57 -204
- package/dist/templates/call-audit/agent.ts +32 -19
- package/dist/templates/call-audit/client.tsx +17 -54
- package/dist/templates/call-audit/workflows/audit.ts +11 -18
- package/dist/templates/call-audit/workflows/ingest.ts +106 -114
- package/dist/templates/call-audit/workflows/media.ts +2 -12
- package/dist/templates/call-audit/workflows/summarize.ts +47 -52
- package/dist/templates/call-audit/workflows/sync-api.ts +7 -7
- package/dist/templates/code-interpreter/agent.eval.test.ts +165 -0
- package/dist/templates/dispatch-center/agent.eval.test.ts +349 -0
- package/dist/templates/dispatch-center/agent.test.ts +149 -25
- package/dist/templates/dispatch-center/client.tsx +239 -129
- package/dist/templates/dispatch-center/shared.ts +99 -1
- package/dist/templates/dispatch-center/system-prompt.md +3 -1
- package/dist/templates/dispatch-center/tools/incident_add_note.ts +16 -13
- package/dist/templates/dispatch-center/tools/incident_create.ts +19 -1
- package/dist/templates/dispatch-center/tools/incident_escalate.ts +68 -59
- package/dist/templates/dispatch-center/tools/incident_triage.ts +57 -43
- package/dist/templates/dispatch-center/tools/incident_update_status.ts +49 -40
- package/dist/templates/dispatch-center/tools/ops_run_scenario.ts +8 -2
- package/dist/templates/dispatch-center/tools/resources_dispatch.ts +69 -52
- package/dist/templates/dispatch-center/tools/resources_update_status.ts +45 -41
- package/dist/templates/embedded-assets/agent.eval.test.ts +138 -0
- package/dist/templates/embedded-assets/agent.test.ts +17 -14
- package/dist/templates/health-assistant/agent.eval.test.ts +167 -0
- package/dist/templates/health-assistant/agent.test.ts +22 -4
- package/dist/templates/infocom-adventure/agent.eval.test.ts +158 -0
- package/dist/templates/infocom-adventure/agent.test.ts +53 -24
- package/dist/templates/infocom-adventure/agent.ts +21 -0
- package/dist/templates/infocom-adventure/client.tsx +246 -164
- package/dist/templates/infocom-adventure/shared.ts +20 -9
- package/dist/templates/infocom-adventure/system-prompt.md +1 -1
- package/dist/templates/infocom-adventure/tools/game_state_move.ts +3 -2
- package/dist/templates/link-digest/agent.eval.test.ts +195 -0
- package/dist/templates/link-digest/agent.test.ts +24 -19
- package/dist/templates/link-digest/client.tsx +47 -61
- package/dist/templates/link-digest/workflows/digest.ts +19 -31
- package/dist/templates/math-buddy/agent.eval.test.ts +146 -0
- package/dist/templates/night-owl/agent.eval.test.ts +272 -0
- package/dist/templates/night-owl/agent.test.ts +70 -19
- package/dist/templates/night-owl/agent.ts +5 -0
- package/dist/templates/night-owl/client.tsx +56 -56
- package/dist/templates/night-owl/shared.ts +24 -0
- package/dist/templates/night-owl/tools/recommend.ts +18 -6
- package/dist/templates/personal-finance/agent.eval.test.ts +168 -0
- package/dist/templates/personal-finance/system-prompt.md +4 -0
- package/dist/templates/pipeline-simple/agent.eval.test.ts +78 -0
- package/dist/templates/pipeline-simple/agent.ts +2 -2
- package/dist/templates/pizza-ordering/agent.eval.test.ts +171 -0
- package/dist/templates/pizza-ordering/agent.test.ts +42 -27
- package/dist/templates/pizza-ordering/client.tsx +9 -26
- package/dist/templates/pizza-ordering/shared.ts +20 -1
- package/dist/templates/pizza-ordering/system-prompt.md +6 -0
- package/dist/templates/plan-and-execute/agent.eval.test.ts +209 -0
- package/dist/templates/plan-and-execute/agent.test.ts +99 -58
- package/dist/templates/plan-and-execute/agent.ts +1 -1
- package/dist/templates/plan-and-execute/client.tsx +12 -15
- package/dist/templates/plan-and-execute/shared.ts +71 -2
- package/dist/templates/plan-and-execute/tools/plan_status.ts +18 -6
- package/dist/templates/plan-and-execute/tools/revise_plan.ts +18 -10
- package/dist/templates/plan-and-execute/tools/start_plan.ts +13 -2
- package/dist/templates/plan-and-execute/tools/work_next_step.ts +70 -22
- package/dist/templates/podcast-digest/agent.eval.test.ts +411 -0
- package/dist/templates/podcast-digest/agent.test.ts +717 -0
- package/dist/templates/podcast-digest/agent.ts +139 -0
- package/dist/templates/podcast-digest/client.tsx +154 -0
- package/dist/templates/podcast-digest/workflows/digest.ts +444 -0
- package/dist/templates/podcast-digest/workflows/feeds.ts +507 -0
- package/dist/templates/podcast-digest/workflows/slack.ts +88 -0
- package/dist/templates/recap-workflow/agent.eval.test.ts +366 -0
- package/dist/templates/recap-workflow/agent.test.ts +97 -94
- package/dist/templates/recap-workflow/tools/recap_progress.ts +9 -12
- package/dist/templates/recap-workflow/tools/recap_status.ts +8 -9
- package/dist/templates/recap-workflow/tools/request_recap.ts +2 -1
- package/dist/templates/recap-workflow/workflows/recap.ts +36 -40
- package/dist/templates/redline/agent.eval.test.ts +209 -0
- package/dist/templates/redline/agent.test.ts +17 -15
- package/dist/templates/redline/client.tsx +12 -12
- package/dist/templates/redline/workflows/redline.ts +19 -31
- package/dist/templates/research-workflow/agent.eval.test.ts +329 -0
- package/dist/templates/research-workflow/agent.test.ts +60 -59
- package/dist/templates/research-workflow/tools/research_progress.ts +9 -12
- package/dist/templates/research-workflow/tools/research_status.ts +9 -11
- package/dist/templates/research-workflow/workflows/research.ts +44 -61
- package/dist/templates/retail/address.ts +87 -1
- package/dist/templates/retail/agent.eval.test.ts +314 -0
- package/dist/templates/retail/agent.test.ts +195 -112
- package/dist/templates/retail/agent.ts +35 -1
- package/dist/templates/retail/cancel.ts +116 -0
- package/dist/templates/retail/client.tsx +265 -117
- package/dist/templates/retail/payment.ts +107 -0
- package/dist/templates/retail/pending.test.ts +196 -0
- package/dist/templates/retail/pending.ts +137 -0
- package/dist/templates/retail/registry.test.ts +150 -7
- package/dist/templates/retail/returns.ts +102 -0
- package/dist/templates/retail/shared.test.ts +42 -0
- package/dist/templates/retail/shared.ts +35 -0
- package/dist/templates/retail/store.test.ts +84 -15
- package/dist/templates/retail/store.ts +231 -48
- package/dist/templates/retail/swap.test.ts +4 -4
- package/dist/templates/retail/swap.ts +248 -15
- package/dist/templates/retail/system-prompt.md +69 -28
- package/dist/templates/retail/tools/cancel_change.ts +33 -0
- package/dist/templates/retail/tools/cancel_pending_order.ts +16 -60
- package/dist/templates/retail/tools/confirm_change.ts +42 -0
- package/dist/templates/retail/tools/exchange_delivered_order_items.ts +22 -67
- package/dist/templates/retail/tools/find_user_id_by_email.ts +8 -5
- package/dist/templates/retail/tools/find_user_id_by_name_zip.ts +4 -5
- package/dist/templates/retail/tools/get_item_details.ts +3 -4
- package/dist/templates/retail/tools/get_order_details.ts +3 -4
- package/dist/templates/retail/tools/get_product_details.ts +3 -4
- package/dist/templates/retail/tools/get_user_details.ts +2 -2
- package/dist/templates/retail/tools/list_all_product_types.ts +5 -5
- package/dist/templates/retail/tools/modify_pending_order_address.ts +13 -25
- package/dist/templates/retail/tools/modify_pending_order_items.ts +23 -62
- package/dist/templates/retail/tools/modify_pending_order_payment.ts +16 -78
- package/dist/templates/retail/tools/modify_user_address.ts +13 -16
- package/dist/templates/retail/tools/return_delivered_order_items.ts +17 -72
- package/dist/templates/retail/tools/transfer_to_human_agents.ts +12 -6
- package/dist/templates/simple/agent.eval.test.ts +54 -0
- package/dist/templates/solo-rpg/agent.eval.test.ts +320 -0
- package/dist/templates/solo-rpg/agent.test.ts +227 -48
- package/dist/templates/solo-rpg/client.tsx +38 -37
- package/dist/templates/solo-rpg/shared.ts +145 -19
- package/dist/templates/solo-rpg/system-prompt.md +3 -2
- package/dist/templates/solo-rpg/tools/action_roll.ts +82 -63
- package/dist/templates/solo-rpg/tools/burn_momentum.ts +85 -50
- package/dist/templates/solo-rpg/tools/check_state.ts +24 -7
- package/dist/templates/solo-rpg/tools/load_game.ts +13 -1
- package/dist/templates/solo-rpg/tools/save_game.ts +16 -7
- package/dist/templates/solo-rpg/tools/setup_character.ts +22 -2
- package/dist/templates/solo-rpg/tools/update_state.ts +113 -100
- package/dist/templates/spoken-summary/agent.eval.test.ts +342 -0
- package/dist/templates/spoken-summary/agent.test.ts +68 -97
- package/dist/templates/spoken-summary/agent.ts +30 -17
- package/dist/templates/spoken-summary/client.tsx +10 -50
- package/dist/templates/spoken-summary/workflows/summarize.ts +17 -22
- package/dist/templates/spoken-summary/workflows/transcribe.ts +19 -26
- package/dist/templates/support-line/agent.eval.test.ts +273 -0
- package/dist/templates/support-line/agent.test.ts +11 -16
- package/dist/templates/support-line/agent.ts +1 -1
- package/dist/templates/support-line/client.tsx +9 -9
- package/dist/templates/support-line/nodes.ts +100 -0
- package/dist/templates/support-line/procedure.ts +407 -0
- package/dist/templates/support-line/system-prompt.md +2 -1
- package/dist/templates/support-line/tools/answer_question.ts +66 -4
- package/dist/templates/transcription-workflow/agent.eval.test.ts +234 -0
- package/dist/templates/transcription-workflow/agent.test.ts +94 -194
- package/dist/templates/transcription-workflow/agent.ts +1 -1
- package/dist/templates/transcription-workflow/client.tsx +17 -42
- package/dist/templates/transcription-workflow/workflows/batch.ts +19 -22
- package/dist/templates/transcription-workflow/workflows/normalize.ts +90 -186
- package/dist/templates/transcription-workflow/workflows/stitch.ts +0 -11
- package/dist/templates/transcription-workflow/workflows/stream.ts +4 -5
- package/dist/templates/transcription-workflow/workflows/sync-api.ts +6 -6
- package/dist/templates/transcription-workflow/workflows/transcribe.ts +32 -26
- package/dist/templates/transcription-workflow/workflows/wav.ts +0 -31
- package/dist/templates/travel-concierge/agent.eval.test.ts +323 -0
- package/dist/templates/travel-concierge/agent.test.ts +174 -43
- package/dist/templates/travel-concierge/agent.ts +18 -9
- package/dist/templates/travel-concierge/client.tsx +11 -23
- package/dist/templates/travel-concierge/routing.ts +34 -15
- package/dist/templates/travel-concierge/shared.ts +180 -12
- package/dist/templates/travel-concierge/system-prompt.md +7 -1
- package/dist/templates/travel-concierge/tools/book_car_rental.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_excursion.ts +8 -5
- package/dist/templates/travel-concierge/tools/book_hotel.ts +8 -5
- package/dist/templates/travel-concierge/tools/cancel_ticket.ts +8 -5
- package/dist/templates/travel-concierge/tools/search_car_rentals.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_excursions.ts +9 -5
- package/dist/templates/travel-concierge/tools/search_flights.ts +9 -6
- package/dist/templates/travel-concierge/tools/search_hotels.ts +9 -5
- package/dist/templates/travel-concierge/tools/update_ticket.ts +8 -5
- package/dist/templates/web-researcher/agent.eval.test.ts +121 -0
- package/dist/templates/web-researcher/system-prompt.md +15 -4
- package/dist/{test-9kPKJI-w.mjs → test-BvyT6sAc.mjs} +24 -10
- package/dist/test.d.ts +29 -5
- package/dist/{worker-bundler-CGD4r8Kc.mjs → worker-bundler-CCVEDjm1.mjs} +241 -3
- package/dist/worker-bundler.mjs +1 -1
- package/dist/{workflow-CFpxOFfQ.mjs → workflow-BflATMmU.mjs} +47 -12
- package/dist/workflow-bundler.d.ts +6 -1
- package/dist/workflow.d.ts +1 -1
- package/package.json +5 -4
- package/dist/eject-31gjtaHF.mjs +0 -116
- package/dist/eject.d.ts +0 -60
- package/dist/templates/call-audit/workflows/temp-media.ts +0 -138
- package/dist/templates/infocom-adventure/tools/game_state_history.ts +0 -14
- package/dist/templates/support-line/graph.ts +0 -224
- package/dist/{_config-D_s09e7g.mjs → _config-CmJOFsAP.mjs} +1 -1
- /package/dist/templates/plan-and-execute/{graph.ts → procedure.ts} +0 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The desk's two subagents, and the board of what they have found.
|
|
3
|
+
*
|
|
4
|
+
* **This template is the worked example for `ctx.delegate`.** Everything here
|
|
5
|
+
* exists to show the three things a subagent buys that `ctx.generate` cannot:
|
|
6
|
+
*
|
|
7
|
+
* 1. **A context window the caller never pays for.** `researcher` reads whole
|
|
8
|
+
* web pages. A briefing on four angles can spend a hundred thousand tokens
|
|
9
|
+
* doing it, and what comes back into the phone call's conversation is four
|
|
10
|
+
* paragraphs — because {@link DelegateResult.text} is the subagent's final
|
|
11
|
+
* message and nothing else crosses back.
|
|
12
|
+
* 2. **Parallelism.** Four angles are four independent runs, so
|
|
13
|
+
* `tools/research_topic.ts` fans them out with one `Promise.allSettled` and
|
|
14
|
+
* the caller waits for the slowest, not for the sum.
|
|
15
|
+
* 3. **Tools isolated by capability.** `researcher` can read the open web;
|
|
16
|
+
* `factChecker` can only search it, on a cheaper model with a third of the
|
|
17
|
+
* budget. Neither can reach the other's tools, and the DESK — the voice
|
|
18
|
+
* agent the caller is talking to — has no web tools at all.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import {
|
|
22
|
+
type DeepReadonly,
|
|
23
|
+
type DelegateFn,
|
|
24
|
+
type DelegateOptions,
|
|
25
|
+
type DelegateResult,
|
|
26
|
+
pushCapped,
|
|
27
|
+
type SubagentToolCall,
|
|
28
|
+
sessionSlot,
|
|
29
|
+
subagent,
|
|
30
|
+
} from "@alexkroman1/aai";
|
|
31
|
+
import { assemblyAILlm } from "@alexkroman1/aai/llm";
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Steps one angle may take. A budget, not a limit to be raised when an answer
|
|
35
|
+
* disappoints: a subagent told to "keep looking until sure" is a subagent whose
|
|
36
|
+
* cost nobody can quote, and the caller is on the phone. Past it the researcher
|
|
37
|
+
* is asked for its answer with its tools withheld, so a capped run still comes
|
|
38
|
+
* back with prose rather than stopping mid-chain.
|
|
39
|
+
*/
|
|
40
|
+
export const MAX_RESEARCH_STEPS = 6;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The researcher, and the one line that decides whether any of this works.
|
|
44
|
+
*
|
|
45
|
+
* "Finish with a summary" is not politeness. The parent gets the subagent's
|
|
46
|
+
* FINAL message, so a run that ends by saying "Done." has thrown away
|
|
47
|
+
* everything it read and no budget recovers it — this is the failure mode the
|
|
48
|
+
* `SubagentDef.instructions` contract warns about, stated once, here.
|
|
49
|
+
*/
|
|
50
|
+
export const researcher = subagent({
|
|
51
|
+
name: "researcher",
|
|
52
|
+
instructions: [
|
|
53
|
+
"You are a research agent working one angle of a briefing.",
|
|
54
|
+
"",
|
|
55
|
+
"Search, then open the two or three most promising pages and read them.",
|
|
56
|
+
"Prefer primary sources and recent ones. If the sources disagree, say so",
|
|
57
|
+
"rather than picking a side.",
|
|
58
|
+
"",
|
|
59
|
+
"IMPORTANT: your FINAL message is the only thing the desk receives — it",
|
|
60
|
+
"does not see your searches, the pages you opened, or your reasoning. End",
|
|
61
|
+
"with a self-contained paragraph of what you found, naming the sources you",
|
|
62
|
+
"trusted. Three sentences is plenty; do not write a report.",
|
|
63
|
+
].join("\n"),
|
|
64
|
+
// Read/browse. Independent of the desk's own builtins, which are none: the
|
|
65
|
+
// agent the caller talks to never touches the network.
|
|
66
|
+
builtinTools: ["web_search", "visit_webpage"],
|
|
67
|
+
maxSteps: MAX_RESEARCH_STEPS,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The fact-checker: a second ROLE, deliberately narrower than the first.
|
|
72
|
+
*
|
|
73
|
+
* Its own `llm` (cheaper and quicker — checking one sentence is not the job
|
|
74
|
+
* `researcher` does), its own budget, and search only. That split is the third
|
|
75
|
+
* reason to reach for a subagent: a capability a run does not need is one it
|
|
76
|
+
* cannot misuse.
|
|
77
|
+
*/
|
|
78
|
+
export const factChecker = subagent({
|
|
79
|
+
name: "fact-checker",
|
|
80
|
+
instructions: [
|
|
81
|
+
"You check ONE claim against what you can find on the web.",
|
|
82
|
+
"",
|
|
83
|
+
"Search for it. Answer in one sentence, starting with one of",
|
|
84
|
+
"'Confirmed:', 'Contradicted:' or 'Unclear:', and name what you found.",
|
|
85
|
+
"'Unclear' is a real answer — say it rather than guessing.",
|
|
86
|
+
].join("\n"),
|
|
87
|
+
llm: assemblyAILlm({ model: "gemini-2.5-flash-lite" }),
|
|
88
|
+
builtinTools: ["web_search"],
|
|
89
|
+
maxSteps: 2,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
/** One angle, as the desk holds it. */
|
|
93
|
+
export interface Finding {
|
|
94
|
+
/** The angle the researcher was given. */
|
|
95
|
+
angle: string;
|
|
96
|
+
/** Its final message — the whole of what crossed back. */
|
|
97
|
+
summary: string;
|
|
98
|
+
/** Searches it ran and pages it opened, for narrating the wait. */
|
|
99
|
+
work: AngleWork;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** What one angle COST, as the desk is willing to say it out loud. */
|
|
103
|
+
export interface AngleWork {
|
|
104
|
+
searches: number;
|
|
105
|
+
reads: number;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The brief one angle is sent with.
|
|
110
|
+
*
|
|
111
|
+
* Its own function because the `context` line is the whole of what a
|
|
112
|
+
* researcher learns about the call it belongs to — a subagent's context is
|
|
113
|
+
* ISOLATED, so anything the conversation knows and the angle does not say is
|
|
114
|
+
* lost unless it is written here. A spec asserts on this rather than on a
|
|
115
|
+
* template literal buried in a tool body.
|
|
116
|
+
*/
|
|
117
|
+
export function angleBrief(topic: string, angle: string): DelegateOptions {
|
|
118
|
+
return { task: angle, context: `This angle belongs to a briefing on: ${topic}.` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* What a run did, read off the calls it made.
|
|
123
|
+
*
|
|
124
|
+
* {@link DelegateResult.toolCalls} carries the CALLS and not their results —
|
|
125
|
+
* the results are what stayed in the subagent's window — so this is the most
|
|
126
|
+
* the desk can honestly say about the wait. An unrecognised tool name counts
|
|
127
|
+
* as neither: a researcher that gains a third tool should not silently inflate
|
|
128
|
+
* "searches".
|
|
129
|
+
*/
|
|
130
|
+
export function countWork(toolCalls: readonly SubagentToolCall[]): AngleWork {
|
|
131
|
+
let searches = 0;
|
|
132
|
+
let reads = 0;
|
|
133
|
+
for (const call of toolCalls) {
|
|
134
|
+
if (call.name === "web_search") searches += 1;
|
|
135
|
+
else if (call.name === "visit_webpage") reads += 1;
|
|
136
|
+
}
|
|
137
|
+
return { searches, reads };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Run one angle and reduce it to what the board holds.
|
|
142
|
+
*
|
|
143
|
+
* Takes the DELEGATE rather than the whole tool context — the same seam
|
|
144
|
+
* `plan-and-execute` puts on `ctx.generate`. The desk's own logic is then
|
|
145
|
+
* exercised against a fake without a context at all, and the tool that fans
|
|
146
|
+
* these out stays one line per angle.
|
|
147
|
+
*/
|
|
148
|
+
export async function researchAngle(
|
|
149
|
+
delegate: DelegateFn,
|
|
150
|
+
topic: string,
|
|
151
|
+
angle: string,
|
|
152
|
+
): Promise<Finding> {
|
|
153
|
+
const result: DelegateResult = await delegate(researcher, angleBrief(topic, angle));
|
|
154
|
+
return { angle, summary: result.text, work: countWork(result.toolCalls) };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Angles one `research_topic` call may fan out. Four researchers at once is
|
|
158
|
+
* already four model bills; past that a caller is waiting on a queue. */
|
|
159
|
+
export const MAX_ANGLES = 4;
|
|
160
|
+
|
|
161
|
+
/** Findings the board holds. Older ones fall off — they ride in every prompt
|
|
162
|
+
* the desk builds and in every recap it reads back. */
|
|
163
|
+
export const MAX_FINDINGS = 12;
|
|
164
|
+
|
|
165
|
+
export interface BriefingState {
|
|
166
|
+
/** What the caller asked about, as last stated. */
|
|
167
|
+
topic: string | null;
|
|
168
|
+
/** Every angle researched on this call, oldest first. */
|
|
169
|
+
findings: Finding[];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function emptyBriefing(): BriefingState {
|
|
173
|
+
return { topic: null, findings: [] };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const briefingSlot = sessionSlot("briefing", emptyBriefing);
|
|
177
|
+
|
|
178
|
+
/** Record a finding, holding {@link MAX_FINDINGS}. */
|
|
179
|
+
export function recordFinding(state: BriefingState, finding: Finding): void {
|
|
180
|
+
pushCapped(state.findings, finding, MAX_FINDINGS);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* The board as a READ hands it out — deep-frozen, and typed to say so, which is
|
|
185
|
+
* what a slot's read returns.
|
|
186
|
+
*/
|
|
187
|
+
export type FrozenBriefing = DeepReadonly<BriefingState>;
|
|
188
|
+
|
|
189
|
+
/** A finding named by what the caller would say: its angle, loosely matched. */
|
|
190
|
+
export function findByAngle(
|
|
191
|
+
state: FrozenBriefing,
|
|
192
|
+
angle: string,
|
|
193
|
+
): DeepReadonly<Finding> | undefined {
|
|
194
|
+
const wanted = angle.trim().toLowerCase();
|
|
195
|
+
if (wanted === "") return undefined;
|
|
196
|
+
return state.findings.find(
|
|
197
|
+
(finding) =>
|
|
198
|
+
finding.angle.toLowerCase().includes(wanted) || wanted.includes(finding.angle.toLowerCase()),
|
|
199
|
+
);
|
|
200
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
You are the Briefing Desk. Someone has phoned you to find out about something,
|
|
2
|
+
and you have a small team of researchers you can put on it.
|
|
3
|
+
|
|
4
|
+
## How you work
|
|
5
|
+
|
|
6
|
+
- You have no way to look anything up yourself. Everything you know about the
|
|
7
|
+
outside world comes back from `research_topic` or `verify_claim`.
|
|
8
|
+
- When the caller names a subject, decide the two or three ANGLES worth
|
|
9
|
+
researching and call `research_topic` once with all of them. Do not call it
|
|
10
|
+
once per angle — they run in parallel, and that is why the caller is not
|
|
11
|
+
waiting four times over.
|
|
12
|
+
- Each angle must stand on its own. A researcher has not heard this call and
|
|
13
|
+
cannot see the others' work, so "the same but for Europe" is not an angle;
|
|
14
|
+
"how European home battery prices moved in 2025" is.
|
|
15
|
+
- Say you are looking it up BEFORE you call the tool. A silent line is the
|
|
16
|
+
worst thing that can happen on a phone call, and a briefing takes a moment.
|
|
17
|
+
|
|
18
|
+
## How you talk
|
|
19
|
+
|
|
20
|
+
- Lead with the through-line — what it all adds up to — in one or two
|
|
21
|
+
sentences. Then the angles, one at a time, in the order that makes the point.
|
|
22
|
+
- Never read a summary out verbatim. They are written to be read, and you are
|
|
23
|
+
being listened to: shorten, and say the number rather than the sentence
|
|
24
|
+
around it.
|
|
25
|
+
- Say where something came from when it matters ("the manufacturers' own
|
|
26
|
+
figures say", "one industry blog says") and say when your researchers
|
|
27
|
+
disagreed. A briefing that flattens a disagreement is worse than no briefing.
|
|
28
|
+
- If the caller pushes back on a fact, check it with `verify_claim` rather than
|
|
29
|
+
defending it. If it comes back contradicted, correct yourself plainly.
|
|
30
|
+
- `briefing_so_far` is for a recap. It costs nothing, so use it rather than
|
|
31
|
+
reciting from memory.
|
|
32
|
+
|
|
33
|
+
## What not to do
|
|
34
|
+
|
|
35
|
+
- Do not answer a factual question about the world from memory. If you have not
|
|
36
|
+
had it researched on this call, say so and offer to look it up.
|
|
37
|
+
- Do not offer more angles than you can hold. Three good ones beat six.
|
|
38
|
+
- Do not describe your researchers, your tools, or how any of this works unless
|
|
39
|
+
the caller asks. They phoned a desk, not an architecture.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { tool } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { briefingSlot } from "../shared.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Read the board back.
|
|
7
|
+
*
|
|
8
|
+
* The one tool here that spends no model at all, and it earns its place for
|
|
9
|
+
* that reason: a caller who says "what have you got so far" after four
|
|
10
|
+
* delegated runs should not cost a fifth. What the desk knows is on the slot;
|
|
11
|
+
* this hands it over.
|
|
12
|
+
*/
|
|
13
|
+
export default tool({
|
|
14
|
+
description:
|
|
15
|
+
"List the angles researched on this call and what each one found. Use it " +
|
|
16
|
+
"when the caller asks for a recap, or before you offer to dig further.",
|
|
17
|
+
inputSchema: z.object({}),
|
|
18
|
+
execute: (_args, ctx) => {
|
|
19
|
+
const board = briefingSlot.get(ctx);
|
|
20
|
+
if (board.findings.length === 0) {
|
|
21
|
+
return {
|
|
22
|
+
topic: null,
|
|
23
|
+
findings: [],
|
|
24
|
+
message: "Nothing researched yet — ask what they want looked into.",
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
topic: board.topic,
|
|
29
|
+
findings: board.findings,
|
|
30
|
+
totalSearches: board.findings.reduce((sum, finding) => sum + finding.work.searches, 0),
|
|
31
|
+
totalReads: board.findings.reduce((sum, finding) => sum + finding.work.reads, 0),
|
|
32
|
+
message: "Recap the through-line in one breath, then offer to go deeper on one angle.",
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { errorMessage, tool, toolFailure } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import type { Finding } from "../shared.ts";
|
|
4
|
+
import { briefingSlot, MAX_ANGLES, recordFinding, researchAngle } from "../shared.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Fan a topic out across angles, one researcher subagent each, in PARALLEL.
|
|
8
|
+
*
|
|
9
|
+
* **The `Promise.allSettled` is the template's whole point.** Each angle is an
|
|
10
|
+
* independent run with its own context window, so four of them cost the caller
|
|
11
|
+
* the SLOWEST one rather than the sum — and nothing they read reaches this
|
|
12
|
+
* conversation, only what each one concluded. Running the same four angles as
|
|
13
|
+
* four `ctx.generate` calls in one loop would be slower and would put every
|
|
14
|
+
* intermediate page in the desk's own prompt.
|
|
15
|
+
*
|
|
16
|
+
* **`allSettled`, not `all`.** One angle that fails (a provider hiccup, a
|
|
17
|
+
* subagent that ran out of budget with nothing to say) must not sink a
|
|
18
|
+
* briefing whose other three came back — a caller on the phone would rather
|
|
19
|
+
* hear three answers and one apology than an error. The failures are reported
|
|
20
|
+
* BY ANGLE so the desk can say which one is missing and offer to retry it.
|
|
21
|
+
*
|
|
22
|
+
* **The await comes first, then the mutation.** `slot.update`'s window is
|
|
23
|
+
* synchronous by contract, so the fan-out finishes before the board is touched
|
|
24
|
+
* and the whole write lands at once.
|
|
25
|
+
*/
|
|
26
|
+
export default tool({
|
|
27
|
+
description:
|
|
28
|
+
"Research a topic across several angles at once and report what each one " +
|
|
29
|
+
"found. Use this when the caller asks about something you would need to " +
|
|
30
|
+
"look up. Pick the angles yourself from what they said — two or three is " +
|
|
31
|
+
"usually right. Tell them you are looking it up before you call this.",
|
|
32
|
+
inputSchema: z.object({
|
|
33
|
+
topic: z.string().max(200).describe("The subject, in the caller's own terms"),
|
|
34
|
+
angles: z
|
|
35
|
+
.array(z.string().max(200))
|
|
36
|
+
.min(1)
|
|
37
|
+
.max(MAX_ANGLES)
|
|
38
|
+
.describe(
|
|
39
|
+
"The separate questions to research, one per researcher. Each must " +
|
|
40
|
+
"stand on its own: a researcher has not heard the conversation.",
|
|
41
|
+
),
|
|
42
|
+
}),
|
|
43
|
+
async execute(args, ctx) {
|
|
44
|
+
const angles = args.angles.map((angle) => angle.trim()).filter((angle) => angle.length > 0);
|
|
45
|
+
if (angles.length === 0) {
|
|
46
|
+
return toolFailure("No angles to research — say what the caller wants looked up.");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const settled = await Promise.allSettled(
|
|
50
|
+
angles.map((angle) => researchAngle(ctx.delegate, args.topic, angle)),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Two typed lists rather than one union filtered twice: `"summary" in one`
|
|
54
|
+
// reads fine and narrows nothing a `.filter` result keeps, so the union
|
|
55
|
+
// version needs a cast — the laundering this repo's ratchet counts.
|
|
56
|
+
const summaries: Finding[] = [];
|
|
57
|
+
const failures: { angle: string; error: string }[] = [];
|
|
58
|
+
settled.forEach((outcome, index) => {
|
|
59
|
+
if (outcome.status === "fulfilled") {
|
|
60
|
+
summaries.push(outcome.value);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
failures.push({ angle: angles[index] ?? "", error: errorMessage(outcome.reason) });
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (summaries.length === 0) {
|
|
67
|
+
return toolFailure(
|
|
68
|
+
`Every angle failed. The first said: ${failures[0]?.error ?? "no reason given"}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return briefingSlot.update(ctx, (board) => {
|
|
73
|
+
board.topic = args.topic;
|
|
74
|
+
for (const finding of summaries) recordFinding(board, finding);
|
|
75
|
+
return {
|
|
76
|
+
topic: args.topic,
|
|
77
|
+
findings: summaries,
|
|
78
|
+
failed: failures,
|
|
79
|
+
message:
|
|
80
|
+
"Give the caller the through-line first, in one or two sentences, then " +
|
|
81
|
+
"the angles. Do not read the summaries out verbatim." +
|
|
82
|
+
(failures.length > 0
|
|
83
|
+
? " Mention which angle you could not get to and offer to try it again."
|
|
84
|
+
: ""),
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { errorMessage, omitUndefined, tool, toolFailure } from "@alexkroman1/aai";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { briefingSlot, countWork, factChecker, findByAngle } from "../shared.ts";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check one sentence against the web, on the narrower of the desk's two
|
|
7
|
+
* subagents.
|
|
8
|
+
*
|
|
9
|
+
* **Why this is a second SUBAGENT and not a second prompt.** Checking a claim
|
|
10
|
+
* needs a search and one sentence back, so it runs on a cheaper model with a
|
|
11
|
+
* third of the researcher's budget and search only — no page reads. Expressing
|
|
12
|
+
* that as `ctx.generate` would mean the desk doing the searching itself, in the
|
|
13
|
+
* conversation's own context; expressing it as one subagent with two modes
|
|
14
|
+
* would mean a run that can browse whenever the model feels like it. A
|
|
15
|
+
* capability a run does not need is one it cannot misuse.
|
|
16
|
+
*
|
|
17
|
+
* **The claim may be quoted from the board.** The caller says "check the second
|
|
18
|
+
* thing you told me", so `about` names an angle and the claim is read out of
|
|
19
|
+
* the slot — a subagent has not heard the call, and handing it "the second
|
|
20
|
+
* thing" as its task would get a confident answer about nothing.
|
|
21
|
+
*/
|
|
22
|
+
export default tool({
|
|
23
|
+
description:
|
|
24
|
+
"Check one specific factual claim against the web. Use it when the caller " +
|
|
25
|
+
"pushes back on something, or asks whether a finding is right. Pass the " +
|
|
26
|
+
"claim as a complete sentence.",
|
|
27
|
+
inputSchema: z.object({
|
|
28
|
+
claim: z
|
|
29
|
+
.string()
|
|
30
|
+
.max(400)
|
|
31
|
+
.describe("The claim to check, stated as one self-contained sentence"),
|
|
32
|
+
about: z
|
|
33
|
+
.string()
|
|
34
|
+
.max(200)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe(
|
|
37
|
+
"The angle this claim came from, when the caller is pointing at " +
|
|
38
|
+
"something already on the board",
|
|
39
|
+
),
|
|
40
|
+
}),
|
|
41
|
+
async execute(args, ctx) {
|
|
42
|
+
const claim = args.claim.trim();
|
|
43
|
+
if (claim === "") return toolFailure("Nothing to check — say the claim in a full sentence.");
|
|
44
|
+
|
|
45
|
+
const board = briefingSlot.get(ctx);
|
|
46
|
+
const source = args.about ? findByAngle(board, args.about) : undefined;
|
|
47
|
+
|
|
48
|
+
let verdict: string;
|
|
49
|
+
let searches: number;
|
|
50
|
+
try {
|
|
51
|
+
// What the checker gets of the conversation, and no more: the finding the
|
|
52
|
+
// claim came out of, so it can tell a misquote from a disagreement.
|
|
53
|
+
// Through `omitUndefined` rather than a truthiness-guarded spread — the
|
|
54
|
+
// repo's one spelling for an optional field, and the shape its own
|
|
55
|
+
// `guard-invariants` rule 22 counts as debt.
|
|
56
|
+
const context = source ? `The desk told the caller: ${source.summary}` : undefined;
|
|
57
|
+
const result = await ctx.delegate(factChecker, {
|
|
58
|
+
task: claim,
|
|
59
|
+
...omitUndefined({ context }),
|
|
60
|
+
});
|
|
61
|
+
verdict = result.text;
|
|
62
|
+
searches = countWork(result.toolCalls).searches;
|
|
63
|
+
} catch (err: unknown) {
|
|
64
|
+
return toolFailure(`The check did not come back: ${errorMessage(err)}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
claim,
|
|
69
|
+
verdict,
|
|
70
|
+
searches,
|
|
71
|
+
checkedAgainst: source?.angle ?? null,
|
|
72
|
+
message:
|
|
73
|
+
"Say the verdict plainly, in the caller's words. If it is contradicted, " +
|
|
74
|
+
"correct what you told them earlier rather than defending it.",
|
|
75
|
+
};
|
|
76
|
+
},
|
|
77
|
+
});
|