@companion-ai/feynman 0.5.22 → 0.5.23

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.
@@ -17,6 +17,7 @@ Tools:
17
17
 
18
18
  Delegation:
19
19
  - Use the `researcher`, `writer`, `verifier`, and `reviewer` subagents when decomposition reduces context pressure or parallelizes evidence gathering. Keep delegation internal; do not make the user manage it.
20
+ - To run several subagents in one call, write the workflow script to `outputs/.plans/<slug>-workflow.js`, and only after that write has succeeded, in a later tool call, call `subagent` with `workflow` set to that path. Do not use `workflow: true`: it needs a fenced block in the visible reply and fails when the block is only in your reasoning.
20
21
  - A workflow is finished only when its final artifact is on disk. Before your final response, wait with `bg_wait` for every subagent whose result the artifact still needs; a launch receipt is not a result.
21
22
  - For long workflows, write the plan to disk early and keep its task ledger and verification log current. If `CHANGELOG.md` exists in the workspace, read it before resuming substantial work and append concise entries after meaningful progress, failures, verification results, or blockers. Do not create it for one-shot tasks.
22
23
  - Prefer the smallest investigation or experiment that can reduce uncertainty. When an experiment is warranted, write the code, run it, and save outputs to disk.
package/RELEASES.md CHANGED
@@ -6,6 +6,12 @@ GitHub release notes are generated from the matching `## vX.Y.Z` section in this
6
6
 
7
7
  ## Unreleased
8
8
 
9
+ ## v0.5.23 - 2026-10-02
10
+
11
+ - **arXiv and PubMed rate limits.** arXiv asks for one request every three seconds, and parallel lookups got `429`; arXiv requests now run at that pace and a 429 is retried once. A PubMed `429`, which a subagent in another process or a shared IP can still trigger, is retried once after a pause.
12
+ - Running several subagents at once from any workflow no longer fails with "workflow: true requires exactly one ```js workflow fenced block": Feynman now always writes the workflow script to a file and runs it by path, as `/deepresearch` already did.
13
+ - Pi 1.0 is not in this release: pi-subagents 0.74.0 cannot start background subagents on it. The fix is merged upstream (nicobailon/pi-subagents#2634) and Feynman will move to Pi 1.0 once it is released.
14
+
9
15
  ## v0.5.22 - 2026-10-01
10
16
 
11
17
  - **Launch warnings and the first-run telemetry notice show inside the session.** Feynman printed them to the terminal just before starting Pi, where Pi 1.0's fullscreen interface (coming in a later Feynman release) hides them for the whole session. They now appear in Pi's own interface: the Windows "No Bash found" warning, the small-local-model warning for research workflows, and the one-time telemetry disclosure. A one-shot `--prompt` run still prints them to stderr.
@@ -1,5 +1,7 @@
1
1
  import { XMLParser } from "fast-xml-parser";
2
2
 
3
+ import { createRequestPacer } from "./request-pacer.js";
4
+
3
5
  type SearchParams = {
4
6
  query: string;
5
7
  };
@@ -27,20 +29,32 @@ function cleanQuery(query: string): string {
27
29
  return clean;
28
30
  }
29
31
 
32
+ // arXiv's API terms ask for no more than one request every three seconds, and
33
+ // parallel lookups got 429. Requests run one after another at that pace, and a
34
+ // 429 is retried once after the same wait.
35
+ const arxivPacer = createRequestPacer();
36
+ export const ARXIV_MIN_GAP_MS = 3000;
37
+
30
38
  async function fetchText(url: URL, accept: string): Promise<string> {
31
- const controller = new AbortController();
32
- const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
33
- try {
34
- const response = await fetch(url, {
35
- headers: { accept },
36
- signal: controller.signal,
39
+ for (let attempt = 0; ; attempt += 1) {
40
+ const result = await arxivPacer(ARXIV_MIN_GAP_MS, async () => {
41
+ const controller = new AbortController();
42
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
43
+ try {
44
+ const response = await fetch(url, {
45
+ headers: { accept },
46
+ signal: controller.signal,
47
+ });
48
+ if (response.status === 429 && attempt === 0) return undefined;
49
+ if (!response.ok) {
50
+ throw new Error(`${url.hostname} request failed: ${response.status} ${response.statusText}`);
51
+ }
52
+ return await response.text();
53
+ } finally {
54
+ clearTimeout(timeout);
55
+ }
37
56
  });
38
- if (!response.ok) {
39
- throw new Error(`${url.hostname} request failed: ${response.status} ${response.statusText}`);
40
- }
41
- return response.text();
42
- } finally {
43
- clearTimeout(timeout);
57
+ if (result !== undefined) return result;
44
58
  }
45
59
  }
46
60
 
@@ -98,23 +98,32 @@ function prune<T extends Record<string, unknown>>(record: T): Record<string, unk
98
98
 
99
99
  // The abort timer stays armed across the body read: fetch resolves on headers,
100
100
  // so clearing it earlier would leave a stalled body hanging forever.
101
+ // Pacing keeps one process under NCBI's limit, but subagents run in their own
102
+ // processes and an IP can be shared, so a 429 is retried once after a pause.
103
+ const NCBI_RETRY_MS = 1500;
104
+
101
105
  async function send<T>(url: URL, accept: string, read: (response: Response) => Promise<T>): Promise<T> {
102
- return withNcbiRateLimit(url, async () => {
103
- const controller = new AbortController();
104
- const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
105
- try {
106
- const response = await fetch(url, {
107
- headers: {
108
- accept,
109
- "user-agent": "feynman-pubmed-tools/1.0 (https://github.com/Companion-Inc/feynman)",
110
- },
111
- signal: controller.signal,
112
- });
113
- return await read(response);
114
- } finally {
115
- clearTimeout(timeout);
116
- }
117
- });
106
+ for (let attempt = 0; ; attempt += 1) {
107
+ const result = await withNcbiRateLimit(url, async (): Promise<{ retry: true } | { value: T }> => {
108
+ const controller = new AbortController();
109
+ const timeout = setTimeout(() => controller.abort(), requestTimeoutMs);
110
+ try {
111
+ const response = await fetch(url, {
112
+ headers: {
113
+ accept,
114
+ "user-agent": "feynman-pubmed-tools/1.0 (https://github.com/Companion-Inc/feynman)",
115
+ },
116
+ signal: controller.signal,
117
+ });
118
+ if (response.status === 429 && attempt === 0) return { retry: true };
119
+ return { value: await read(response) };
120
+ } finally {
121
+ clearTimeout(timeout);
122
+ }
123
+ });
124
+ if ("value" in result) return result.value;
125
+ await new Promise((resolve) => setTimeout(resolve, NCBI_RETRY_MS));
126
+ }
118
127
  }
119
128
 
120
129
  function assertOk(response: Response): void {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@companion-ai/feynman",
3
- "version": "0.5.22",
3
+ "version": "0.5.23",
4
4
  "description": "Research-first CLI agent built on Pi and alphaXiv",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -68,7 +68,7 @@ If subagents were chosen:
68
68
  - Keep `subagent` tool-call JSON small and valid.
69
69
  - Do not place multi-paragraph instructions inside the `subagent` JSON.
70
70
  - Use only supported `subagent` keys. Do not add extra keys such as `artifacts` unless the tool schema explicitly exposes them.
71
- - For parallel evidence gathering, write a workflow script that returns `await runs.all(...)` to `outputs/.plans/<slug>-workflow.js` with the write tool. After the write succeeds, call `subagent` with `workflow: "./outputs/.plans/<slug>-workflow.js"`, `async: true`, and `globalConcurrencyLimit: 4`. Each item needs a unique stable `key`, plus its agent, short task, and output path. Do not pass the script text inside the tool call.
71
+ - For parallel evidence gathering, write a workflow script that returns `await runs.all(...)` to `outputs/.plans/<slug>-workflow.js` with the write tool. After the write succeeds, in a later tool call (not in parallel with the write), call `subagent` with `workflow: "./outputs/.plans/<slug>-workflow.js"`, `async: true`, and `globalConcurrencyLimit: 4`. Each item needs a unique stable `key`, plus its agent, short task, and output path. Do not pass the script text inside the tool call.
72
72
  - Read the ordered result array and record each child's `ok`, error, and returned output/artifact paths. Ordinary child failures are collected by `runs.all`; validation or infrastructure failure can still fail the workflow. Do not assume every output exists.
73
73
  - Prefer broad guidance such as "use paper search and web search"; if a PDF parser or paper fetch fails, the researcher must continue from metadata, abstracts, and web sources and mark PDF parsing as blocked.
74
74
 
@@ -116,7 +116,7 @@ Briefly summarize: "Source is ~<chars> chars -> <N> chunks -> <N> researcher sub
116
116
 
117
117
  ### 3c. Dispatch researcher subagents
118
118
 
119
- Use one async workflow with a stable key for each chunk and a concurrency limit of four: write the script to `outputs/.plans/<slug>-workflow.js` with the write tool, then, after the write succeeds, call `subagent` with `{ "workflow": "./outputs/.plans/<slug>-workflow.js", "async": true, "globalConcurrencyLimit": 4 }`. Expand the example item below for the actual chunk inventory; retain the single-source restriction and explicit output declaration for every child.
119
+ Use one async workflow with a stable key for each chunk and a concurrency limit of four: write the script to `outputs/.plans/<slug>-workflow.js` with the write tool, then, after the write succeeds, in a later tool call, call `subagent` with `{ "workflow": "./outputs/.plans/<slug>-workflow.js", "async": true, "globalConcurrencyLimit": 4 }`. Expand the example item below for the actual chunk inventory; retain the single-source restriction and explicit output declaration for every child.
120
120
 
121
121
  ```js
122
122
  return await runs.all([