@aexhq/agentloop-pi 6.0.0 → 6.1.1

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
@@ -13,8 +13,10 @@ pi's per-turn contract:
13
13
  `contextWindow - reserveTokens` (default 16384), history older than ~`keepRecentTokens`
14
14
  (default 20000) is summarized into pi's structured context checkpoint.
15
15
 
16
- pi's steering and follow-up queues and per-tool `executionMode` are host-app seams with no Brain
17
- equivalent and are not ported.
16
+ Tools and their Environments coordinate access to shared resources with their own transactions,
17
+ locks or queues. A lock prevents overlap; dependency-sensitive work still needs ordered dispatch
18
+ or a Tool that owns the whole operation. Pi has no per-tool scheduling flag or batch Tool protocol.
19
+ Its steering and follow-up queues are host application concerns and are not ported.
18
20
 
19
21
  ```ts
20
22
  import { brainEnv } from "@aexhq/brain";
@@ -32,7 +34,10 @@ The component is built by this package's publisher. Brain consumes the resulting
32
34
  does not compile its JavaScript source.
33
35
 
34
36
  The loop reads paginated session Events before each turn and saves its observation cursor in
35
- kv. Interrupted turns and environment failures enter the transcript as runtime observations.
37
+ kv after saving those observations in the transcript. For unanswered calls in the last saved
38
+ assistant message, it inserts error Tool results explaining that the turn was interrupted and
39
+ the operation may have run. Saved results, media and native provider state are preserved.
40
+ Interrupted turns and environment failures also enter the transcript as runtime observations.
36
41
  A failed tool result goes back to the model with `is_error`; the loop does not retry it automatically.
37
42
  Brain can release execution between turns without losing this transcript or cursor. The caller
38
43
  controls Environment lifetime; a browser closure or provider resource loss can still destroy its
@@ -48,16 +53,31 @@ Transcript changes and `ctx.kv.read/put/delete` use Brain's state services. KV m
48
53
  committed inline; missing keys remain distinct from stored JSON null. A later model or
49
54
  Tool failure preserves acknowledged writes; turn output contains only the result. Retained
50
55
  native model blocks pass through unchanged, and user images enter model context as media.
51
- Version 5.1 also presents successful Tool outputs shaped as
56
+ Successful Tool outputs shaped as
52
57
  `{ type: "aex_tool_output", version: 1, content, media: [{ type: "image", url }] }`
53
- as Tool-result content and image media. Browser and MCP extensions use this presentation
54
- convention; ordinary JSON results keep their existing behavior.
58
+ become Tool-result content and native media. Images and PDFs require HTTPS URLs. Browser and MCP
59
+ extensions use this presentation convention; ordinary JSON/base64 remains business data.
60
+ Results keep call IDs and assistant source order, including mixed image/text batches and failed
61
+ siblings. For hosted applications, publish bytes with Aex's attachment API and return its media URL.
55
62
  Compaction explicitly resets the response format and installs a summary only after `end_turn`;
56
63
  a truncated, refused, or unknown summary leaves the original saved context intact.
57
64
 
58
- Version 5.1 targets Brain 0.22 and preserves the existing WIT. Existing sessions keep their
65
+ Version 6.1 targets Brain SDK 0.24 and preserves the 0.23 WIT and URL-media contract. Existing sessions keep their
59
66
  immutable loop implementation; create new sessions to adopt the updated loop. Keep matching
60
67
  server/artifacts for recovery. An upgrade does not migrate or delete session data.
61
68
 
62
69
  Image and PDF blocks remain native media in compaction requests. The JSON-length estimate used
63
70
  for new context does not measure image/PDF tokens; provider context-limit failures remain explicit.
71
+
72
+ Use `session.send(prompt, { output: { type: AnswerSchema } })` for validated application output.
73
+ Pi finishes when the assistant returns no Tool calls; no special terminal Tool is required.
74
+ Ordinary `send()` returns session state, so `idle` alone does not establish turn success.
75
+
76
+ ## Develop the loop
77
+
78
+ From a checkout of this repository, run `npm run test:logic -w packages/loop-pi` or
79
+ `npm run test:logic:watch -w packages/loop-pi`. These execute the existing JavaScript logic tests
80
+ without rebuilding Wasm. Run `npm test -w packages/loop-pi` for the Component build and package
81
+ tests, then the compiled journeys described in the repository README. All release gates remain
82
+ required. A rebuilt Component needs a new admission and session. Package source is not a public
83
+ logic-library entry point, and existing sessions keep their admitted implementation.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/agentloop-pi",
3
- "version": "6.0.0",
3
+ "version": "6.1.1",
4
4
  "description": "Pi-style parallel-Tool Brain extension",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -31,11 +31,13 @@
31
31
  "scripts": {
32
32
  "build": "node ../../tools/build-agentloop.mjs src/component.mjs src/index.mjs dist",
33
33
  "test": "npm run build && node --test *.test.mjs",
34
+ "test:logic": "node --test loop.test.mjs state.test.mjs",
35
+ "test:logic:watch": "node --test --watch loop.test.mjs state.test.mjs",
34
36
  "test:journeys": "node --test test/journeys.mjs",
35
37
  "prepack": "npm run build"
36
38
  },
37
39
  "dependencies": {
38
- "@aexhq/brain": "0.23.0",
40
+ "@aexhq/brain": "0.24.2",
39
41
  "zod": "4.4.3"
40
42
  },
41
43
  "devDependencies": {}
package/src/logic.mjs CHANGED
@@ -67,7 +67,6 @@ export async function runPi(input, context) {
67
67
  const transcript = cloneJson(input.transcript);
68
68
  const observed_sequence = await observeEvents(context, transcript, (await context.kv.read("observed_sequence")) ?? 0);
69
69
  const saved = await context.kv.read("checkpoint");
70
- await context.kv.put("observed_sequence", observed_sequence);
71
70
  const checkpoint = saved === undefined ? { summary: null } : cloneJson(saved);
72
71
  const body = () => checkpoint.summary === null ? transcript : transcript.slice(1);
73
72
  const shouldCompact = () =>
@@ -104,8 +103,9 @@ export async function runPi(input, context) {
104
103
  };
105
104
 
106
105
  transcript.push({ role: "user", content: [{ type: "text", text: input.input.message }, ...(input.input.media ?? [])] });
106
+ await context.setTranscript(transcript);
107
+ await context.kv.put("observed_sequence", observed_sequence);
107
108
  for (;;) {
108
- await context.setTranscript(transcript);
109
109
  if (shouldCompact()) {
110
110
  await compact();
111
111
  await context.setTranscript(transcript);
@@ -127,6 +127,7 @@ export async function runPi(input, context) {
127
127
  role: "user",
128
128
  content: calls.map((call) => ({ type: "tool_result", tool_use_id: call.call_id, content: TRUNCATED_CALL_MESSAGE, is_error: true })),
129
129
  });
130
+ await context.setTranscript(transcript);
130
131
  continue;
131
132
  }
132
133
  const results = await context.dispatch(calls.map(placement.invocation));
@@ -135,6 +136,7 @@ export async function runPi(input, context) {
135
136
  role: "user",
136
137
  content: calls.map(({ call_id }) => toolResult(call_id, byCall.get(call_id))),
137
138
  });
139
+ await context.setTranscript(transcript);
138
140
  }
139
141
  }
140
142