@wrenautomation/raven 0.9.0 → 0.11.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 CHANGED
@@ -12,11 +12,18 @@ Three calls, one record each:
12
12
  |---|---|---|
13
13
  | `plan` | an ask (text, kind, what is known, what the person says: `steer`, rendered first in every prompt), proposals from people or other models, yield rows from earlier runs | a `Plan`: typed queries, each with `why`, `from` (ai or human) and `by` (model id, plan file or person), the sources it may use, the budget, and a snapshot of what raven was given |
14
14
  | `triage` | hits as a source shows them for free (title, channel, length, date, snippet, which sources returned it) | read or skip per hit; the model writes a why on a read only (a skip's is fixed text); the read budget caps it |
15
- | `review` | what a round came to (counts, found lines with their dates, the extractor's verdict and gap per read, the steps' gaps, the sample check, notes) | the edge: `stop`, `again` (a new round from plan) or `back` (rerun one step), with why. On the last round of the budget the edge is `stop` whatever the model says; its why says what the ask still lacks |
15
+ | `review` | what a round came to (counts, found lines with their dates, the extractor's verdict and gap per read, the steps' gaps, the sample check, notes) | the edge: `stop`, `again` (a new round from plan) or `back` (rerun one step), with why. The budget is shown, never enforced here: the runner parks past it |
16
16
 
17
17
  Ask kinds: `web`, `video`, `channel`, `person`, `site`. Each has a default plan file
18
- (prompts, sources, budget, and `age`: one sentence on how much a source's age counts). Copy one, tune it, keep it beside your ledger: that is where a run
19
- learns. Raven never loops; the runner takes the edge.
18
+ (prompts, sources, budget, `age`: one sentence on how much a source's age counts, and `tree`: the
19
+ facets a round's query tree covers and `thin`, the new hits under which a try falls through to the
20
+ next). Copy one,
21
+ tune it, keep it beside your ledger (`weaver learn`): that is where a run learns. Raven never
22
+ loops; the runner takes the edge.
23
+
24
+ Raven is the runner's planner. An agent that drives weaver's commands (`skills/birdwatch`) does
25
+ raven's three decisions itself and uses raven for its types (`Ask`, `Plan`, `PlanFile`, the
26
+ records), the merge of proposals, and the renderers.
20
27
 
21
28
  ## CLI
22
29
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { fill } from "./model.js";
2
2
  export { mergeProposals, type PlanInput, plan, propose, proposePrompt } from "./plan.js";
3
- export { defaultPlanFile, PLAN_KINDS } from "./plans/index.js";
3
+ export { ageOf, defaultPlanFile, PLAN_KINDS, TREE, treeOf } from "./plans/index.js";
4
4
  export { renderHits, renderYield } from "./render.js";
5
5
  export { review, reviewPrompt } from "./review.js";
6
6
  export { dedupeHits, triage, triagePrompt } from "./triage.js";
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export { fill } from "./model.js";
2
2
  export { mergeProposals, plan, propose, proposePrompt } from "./plan.js";
3
- export { defaultPlanFile, PLAN_KINDS } from "./plans/index.js";
3
+ export { ageOf, defaultPlanFile, PLAN_KINDS, TREE, treeOf } from "./plans/index.js";
4
4
  export { renderHits, renderYield } from "./render.js";
5
5
  export { review, reviewPrompt } from "./review.js";
6
6
  export { dedupeHits, triage, triagePrompt } from "./triage.js";
@@ -2,9 +2,16 @@
2
2
  * The default plan file per ask kind. A copy of one, tuned and stored beside a ledger, is how
3
3
  * a run learns: the recipe changes, raven does not. `raven plans <kind>` prints one to start from.
4
4
  */
5
- import { type AskKind, type PlanFile } from "../types.js";
5
+ import { type AskKind, type PlanFile, type TreeGuide } from "../types.js";
6
+ /**
7
+ * The five sides a researcher covers before the reads start. One tree per round; the agent
8
+ * fills each facet with tries, in order of what it expects to work.
9
+ */
10
+ export declare const TREE: TreeGuide;
6
11
  /** The default plan file for a kind, validated, as a fresh copy. */
7
12
  export declare function defaultPlanFile(kind: AskKind): PlanFile;
8
- /** The plan file's word on age; a stored file from before version 4 takes its kind's default. */
13
+ /** The plan file's word on age; a stored file from before version 4 takes its kind's default. Version 5: the review no longer says "at most". Version 6: `tree`, the query tree's guide. */
9
14
  export declare const ageOf: (planFile: PlanFile) => string | undefined;
15
+ /** The plan file's guide for a query tree; a stored file from before version 6 takes its kind's default. */
16
+ export declare const treeOf: (planFile: PlanFile) => TreeGuide;
10
17
  export declare const PLAN_KINDS: AskKind[];
@@ -2,7 +2,7 @@
2
2
  * The default plan file per ask kind. A copy of one, tuned and stored beside a ledger, is how
3
3
  * a run learns: the recipe changes, raven does not. `raven plans <kind>` prints one to start from.
4
4
  */
5
- import { PlanFile as PlanFileShape } from "../types.js";
5
+ import { PlanFile as PlanFileShape, } from "../types.js";
6
6
  const SHAPE_QUERIES = `Return ONLY a JSON object, no prose, exactly this shape:
7
7
  {"queries": [{"terms": "...", "why": "..."}]}`;
8
8
  const SHAPE_HITS = `Return ONLY a JSON object, no prose, exactly this shape:
@@ -33,7 +33,7 @@ Rules:
33
33
  - "why" is one short line a person reads to agree or not. On "again" it is what the next plan reads first.
34
34
 
35
35
  Ask: {ask}
36
- {steer}Round {round} of at most {rounds}. Hits {hits}, reads {reads}, findings proposed {proposed}, kept {kept}.
36
+ {steer}Round {round}; the budget is {rounds} rounds, a guard rail (past it the runner parks and a person decides), not a stop rule. Hits {hits}, reads {reads}, findings proposed {proposed}, kept {kept}.
37
37
  Found (the source's date in brackets when it gives one):
38
38
  {found}
39
39
  Read (quality: rich, thin, off or empty; then the gap, what the ask still lacks):
@@ -44,10 +44,27 @@ Checked (a sample of the kept findings, each against its document):
44
44
  {checks}
45
45
  Notes:
46
46
  {notes}`;
47
+ /**
48
+ * The five sides a researcher covers before the reads start. One tree per round; the agent
49
+ * fills each facet with tries, in order of what it expects to work.
50
+ */
51
+ export const TREE = {
52
+ facets: [
53
+ { name: "number", why: "a figure: a rate, a price, a count, a date" },
54
+ { name: "how", why: "how it is done: a method, a step list, a tool" },
55
+ { name: "who", why: "who does it or buys it, in their own words" },
56
+ { name: "counter", why: "the case against: what fails, who says no" },
57
+ {
58
+ name: "primary",
59
+ why: "the source itself: a study, a filing, a transcript, the maker's own page",
60
+ },
61
+ ],
62
+ thin: 3,
63
+ };
47
64
  const web = {
48
65
  id: "raven/web",
49
66
  kind: "web",
50
- version: 4,
67
+ version: 6,
51
68
  propose: `You plan web searches for one research ask.
52
69
 
53
70
  ${SHAPE_QUERIES}
@@ -74,12 +91,13 @@ Hits:
74
91
  review: REVIEW,
75
92
  sources: ["searxng", "exa", "google"],
76
93
  budget: { queries: 3, reads: 6, rounds: 3, checks: 3 },
94
+ tree: TREE,
77
95
  age: "Prices, tools and numbers that move: prefer the last two years. A principle or a method can be old.",
78
96
  };
79
97
  const video = {
80
98
  id: "raven/video",
81
99
  kind: "video",
82
- version: 4,
100
+ version: 6,
83
101
  propose: `You plan video searches for one research ask: talks, walkthroughs, interviews and reviews on YouTube and podcast feeds.
84
102
 
85
103
  ${SHAPE_QUERIES}
@@ -106,12 +124,13 @@ Hits:
106
124
  review: REVIEW,
107
125
  sources: ["youtube", "podcasts"],
108
126
  budget: { queries: 3, reads: 4, rounds: 2, checks: 3 },
127
+ tree: TREE,
109
128
  age: "Tools and platforms: prefer the last two years. A talk on a principle can be old.",
110
129
  };
111
130
  const channel = {
112
131
  id: "raven/channel",
113
132
  kind: "channel",
114
- version: 4,
133
+ version: 6,
115
134
  propose: `You plan searches that find the people and channels worth following for one research ask: creators, newsletters, podcasts, forums.
116
135
 
117
136
  ${SHAPE_QUERIES}
@@ -136,12 +155,13 @@ Hits:
136
155
  review: REVIEW,
137
156
  sources: ["searxng", "youtube", "exa"],
138
157
  budget: { queries: 3, reads: 6, rounds: 2, checks: 3 },
158
+ tree: TREE,
139
159
  age: "A channel that has not posted in the last year counts for little.",
140
160
  };
141
161
  const person = {
142
162
  id: "raven/person",
143
163
  kind: "person",
144
- version: 4,
164
+ version: 6,
145
165
  propose: `You plan searches about one person or company for one research ask: what they do, what they said, what others say about them.
146
166
 
147
167
  ${SHAPE_QUERIES}
@@ -168,12 +188,13 @@ Hits:
168
188
  review: REVIEW,
169
189
  sources: ["searxng", "exa", "linkedin", "google"],
170
190
  budget: { queries: 4, reads: 6, rounds: 2, checks: 3 },
191
+ tree: TREE,
171
192
  age: "What they do now: prefer the latest pages. Their history can be old.",
172
193
  };
173
194
  const site = {
174
195
  id: "raven/site",
175
196
  kind: "site",
176
- version: 4,
197
+ version: 6,
177
198
  propose: `You plan searches inside one website for one research ask. Every query runs on {site} only.
178
199
 
179
200
  ${SHAPE_QUERIES}
@@ -199,6 +220,7 @@ Hits:
199
220
  review: REVIEW,
200
221
  sources: ["sitemap", "searxng", "fetch"],
201
222
  budget: { queries: 4, reads: 8, rounds: 2, checks: 3 },
223
+ tree: TREE,
202
224
  age: "When two pages of the site disagree, the later one counts.",
203
225
  };
204
226
  const DEFAULTS = { web, video, channel, person, site };
@@ -206,7 +228,9 @@ const DEFAULTS = { web, video, channel, person, site };
206
228
  export function defaultPlanFile(kind) {
207
229
  return PlanFileShape.parse(structuredClone(DEFAULTS[kind]));
208
230
  }
209
- /** The plan file's word on age; a stored file from before version 4 takes its kind's default. */
231
+ /** The plan file's word on age; a stored file from before version 4 takes its kind's default. Version 5: the review no longer says "at most". Version 6: `tree`, the query tree's guide. */
210
232
  export const ageOf = (planFile) => planFile.age ?? DEFAULTS[planFile.kind].age;
233
+ /** The plan file's guide for a query tree; a stored file from before version 6 takes its kind's default. */
234
+ export const treeOf = (planFile) => planFile.tree ?? DEFAULTS[planFile.kind].tree;
211
235
  export const PLAN_KINDS = Object.keys(DEFAULTS);
212
236
  //# sourceMappingURL=index.js.map
package/dist/review.d.ts CHANGED
@@ -1,8 +1,7 @@
1
1
  /**
2
- * After a round: stop, go again from plan, or back to one step. The round budget is a guard
3
- * rail, not a stop rule: before it is spent the model decides and says why. On the last round
4
- * the model still reads the round, so its why says what the ask still lacks, but the edge is
5
- * stop whatever it answers. The runner takes the edge; raven never loops.
2
+ * After a round: stop, go again from plan, or back to one step. The model decides and says why;
3
+ * the round budget is a guard rail it sees (`{round}` of `{rounds}`), never a stop rule. The
4
+ * runner takes the edge and parks past the budget; raven never loops.
6
5
  */
7
6
  import type { Model } from "@wrenautomation/models";
8
7
  import { type Edge, type PlanFile, type Round } from "./types.js";
package/dist/review.js CHANGED
@@ -26,20 +26,9 @@ export function reviewPrompt(round, planFile) {
26
26
  verdicts: lines(round.verdicts.map(verdictLine), "(none)"),
27
27
  });
28
28
  }
29
- /** Told to the model on the last round: its answer cannot be again or back. */
30
- const LAST = "This is the last round: the run stops whatever you answer. Answer stop, and in why say what the ask still lacks, or that it is answered.";
31
29
  export async function review(round, model, options = {}) {
32
30
  const planFile = options.planFile ?? defaultPlanFile(round.ask.kind);
33
- const last = round.round >= round.budget.rounds;
34
- const shown = last ? { ...round, notes: [...round.notes, LAST] } : round;
35
- const { value: out } = await model.ask(reviewPrompt(shown, planFile), EdgeAnswer);
36
- if (last)
37
- return {
38
- next: "stop",
39
- why: `round budget spent (${round.round} of ${round.budget.rounds}): ${out.why}`,
40
- from: "ai",
41
- by: model.id,
42
- };
31
+ const { value: out } = await model.ask(reviewPrompt(round, planFile), EdgeAnswer);
43
32
  const edge = { next: out.next, why: out.why, from: "ai", by: model.id };
44
33
  if (out.next === "back")
45
34
  edge.to = out.to ?? "fetch";
package/dist/types.d.ts CHANGED
@@ -47,6 +47,8 @@ export declare const Query: z.ZodObject<{
47
47
  terms: z.ZodString;
48
48
  site: z.ZodOptional<z.ZodString>;
49
49
  why: z.ZodString;
50
+ facet: z.ZodOptional<z.ZodString>;
51
+ tier: z.ZodOptional<z.ZodNumber>;
50
52
  from: z.ZodEnum<{
51
53
  ai: "ai";
52
54
  human: "human";
@@ -72,9 +74,55 @@ export declare const Proposal: z.ZodObject<{
72
74
  }>;
73
75
  terms: z.ZodString;
74
76
  why: z.ZodString;
77
+ facet: z.ZodOptional<z.ZodString>;
78
+ tier: z.ZodOptional<z.ZodNumber>;
75
79
  }, z.core.$strip>>;
76
80
  }, z.core.$strip>;
77
81
  export type Proposal = z.infer<typeof Proposal>;
82
+ /** One query as a leaf of the tree: what a person would type, and why. */
83
+ export declare const Leaf: z.ZodObject<{
84
+ terms: z.ZodString;
85
+ why: z.ZodString;
86
+ site: z.ZodOptional<z.ZodString>;
87
+ }, z.core.$strip>;
88
+ export type Leaf = z.infer<typeof Leaf>;
89
+ /**
90
+ * One side of the ask a researcher covers: a number, a how, a who, a counter-view, a primary
91
+ * source. `tries` are its alternatives in order: the first runs; the next runs only when the
92
+ * one before it yielded thin (fewer new hits than the guide's `thin`), or when the agent asks.
93
+ */
94
+ export declare const Facet: z.ZodObject<{
95
+ name: z.ZodString;
96
+ why: z.ZodString;
97
+ tries: z.ZodArray<z.ZodArray<z.ZodObject<{
98
+ terms: z.ZodString;
99
+ why: z.ZodString;
100
+ site: z.ZodOptional<z.ZodString>;
101
+ }, z.core.$strip>>>;
102
+ }, z.core.$strip>;
103
+ export type Facet = z.infer<typeof Facet>;
104
+ /** The fan-out for one round: the facets, each with its tries. The agent writes it; weaver runs it. */
105
+ export declare const QueryTree: z.ZodObject<{
106
+ facets: z.ZodArray<z.ZodObject<{
107
+ name: z.ZodString;
108
+ why: z.ZodString;
109
+ tries: z.ZodArray<z.ZodArray<z.ZodObject<{
110
+ terms: z.ZodString;
111
+ why: z.ZodString;
112
+ site: z.ZodOptional<z.ZodString>;
113
+ }, z.core.$strip>>>;
114
+ }, z.core.$strip>>;
115
+ }, z.core.$strip>;
116
+ export type QueryTree = z.infer<typeof QueryTree>;
117
+ /** The plan file's guide for a tree: the facets a tree of this kind covers, and what thin means. A guide, not a cap: a tree may add facets; one it leaves out is a warning. */
118
+ export declare const TreeGuide: z.ZodObject<{
119
+ facets: z.ZodArray<z.ZodObject<{
120
+ name: z.ZodString;
121
+ why: z.ZodString;
122
+ }, z.core.$strip>>;
123
+ thin: z.ZodNumber;
124
+ }, z.core.$strip>;
125
+ export type TreeGuide = z.infer<typeof TreeGuide>;
78
126
  export declare const Budget: z.ZodObject<{
79
127
  queries: z.ZodNumber;
80
128
  reads: z.ZodNumber;
@@ -109,6 +157,13 @@ export declare const PlanFile: z.ZodObject<{
109
157
  checks: z.ZodDefault<z.ZodNumber>;
110
158
  }, z.core.$strip>;
111
159
  age: z.ZodOptional<z.ZodString>;
160
+ tree: z.ZodOptional<z.ZodObject<{
161
+ facets: z.ZodArray<z.ZodObject<{
162
+ name: z.ZodString;
163
+ why: z.ZodString;
164
+ }, z.core.$strip>>;
165
+ thin: z.ZodNumber;
166
+ }, z.core.$strip>>;
112
167
  }, z.core.$strip>;
113
168
  export type PlanFile = z.infer<typeof PlanFile>;
114
169
  /** What a source, channel or proposer yielded before. Raven reads it; wren or the files ledger writes it. */
@@ -168,6 +223,8 @@ export declare const Plan: z.ZodObject<{
168
223
  terms: z.ZodString;
169
224
  site: z.ZodOptional<z.ZodString>;
170
225
  why: z.ZodString;
226
+ facet: z.ZodOptional<z.ZodString>;
227
+ tier: z.ZodOptional<z.ZodNumber>;
171
228
  from: z.ZodEnum<{
172
229
  ai: "ai";
173
230
  human: "human";
@@ -221,8 +278,8 @@ export type Triage = z.infer<typeof Triage>;
221
278
  export declare const Verdict: z.ZodObject<{
222
279
  url: z.ZodString;
223
280
  quality: z.ZodEnum<{
224
- rich: "rich";
225
281
  thin: "thin";
282
+ rich: "rich";
226
283
  off: "off";
227
284
  empty: "empty";
228
285
  }>;
@@ -261,8 +318,8 @@ export declare const Round: z.ZodObject<{
261
318
  verdicts: z.ZodDefault<z.ZodArray<z.ZodObject<{
262
319
  url: z.ZodString;
263
320
  quality: z.ZodEnum<{
264
- rich: "rich";
265
321
  thin: "thin";
322
+ rich: "rich";
266
323
  off: "off";
267
324
  empty: "empty";
268
325
  }>;
package/dist/types.js CHANGED
@@ -28,6 +28,9 @@ export const Query = z.object({
28
28
  site: z.string().optional(),
29
29
  /** Why this query serves the ask: the line a reviewer reads first. */
30
30
  why: z.string(),
31
+ /** The tree's facet it serves, and which try (1 = the first). Absent on a flat query. */
32
+ facet: z.string().optional(),
33
+ tier: z.number().int().positive().optional(),
31
34
  from: From,
32
35
  /** Model id, plan file id or a person's name. */
33
36
  by: z.string(),
@@ -38,6 +41,31 @@ export const Proposal = z.object({
38
41
  by: z.string(),
39
42
  queries: z.array(Query.omit({ from: true, by: true })),
40
43
  });
44
+ /** One query as a leaf of the tree: what a person would type, and why. */
45
+ export const Leaf = z.object({
46
+ terms: z.string().min(1),
47
+ why: z.string(),
48
+ site: z.string().optional(),
49
+ });
50
+ /**
51
+ * One side of the ask a researcher covers: a number, a how, a who, a counter-view, a primary
52
+ * source. `tries` are its alternatives in order: the first runs; the next runs only when the
53
+ * one before it yielded thin (fewer new hits than the guide's `thin`), or when the agent asks.
54
+ */
55
+ export const Facet = z.object({
56
+ name: z.string().min(1),
57
+ /** What this facet would prove. */
58
+ why: z.string(),
59
+ tries: z.array(z.array(Leaf).min(1)).min(1),
60
+ });
61
+ /** The fan-out for one round: the facets, each with its tries. The agent writes it; weaver runs it. */
62
+ export const QueryTree = z.object({ facets: z.array(Facet).min(1) });
63
+ /** The plan file's guide for a tree: the facets a tree of this kind covers, and what thin means. A guide, not a cap: a tree may add facets; one it leaves out is a warning. */
64
+ export const TreeGuide = z.object({
65
+ facets: z.array(z.object({ name: z.string().min(1), why: z.string() })),
66
+ /** New hits under which a try counts as thin, so the next try runs. */
67
+ thin: z.number().int().nonnegative(),
68
+ });
41
69
  export const Budget = z.object({
42
70
  /** Queries per round after the merge. Human queries never count against it. */
43
71
  queries: z.number().int().positive(),
@@ -69,6 +97,8 @@ export const PlanFile = z.object({
69
97
  budget: Budget,
70
98
  /** How much a source's age counts for this kind, one sentence: filled as `{age}`. Never a cutoff. */
71
99
  age: z.string().optional(),
100
+ /** The facets a query tree of this kind covers. Absent on a file from before version 6: the kind's default. */
101
+ tree: TreeGuide.optional(),
72
102
  });
73
103
  /** What a source, channel or proposer yielded before. Raven reads it; wren or the files ledger writes it. */
74
104
  export const YieldRow = z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrenautomation/raven",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Ask in, plan out. The planner of Wren's research engine: typed queries with who proposed them and why, proposals merged into one plan under a plan file per ask kind, read-or-skip on hits, and the edge to take after a round. Pure functions and prompts; the model is injected.",
5
5
  "license": "PolyForm-Strict-1.0.0",
6
6
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "dependencies": {
33
33
  "commander": "15.0.0",
34
34
  "zod": "4.6.5",
35
- "@wrenautomation/models": "0.9.0"
35
+ "@wrenautomation/models": "0.11.0"
36
36
  },
37
37
  "scripts": {
38
38
  "typecheck": "tsc --noEmit",