@aexhq/agentloop-pi 2.1.0 → 3.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 CHANGED
@@ -17,10 +17,10 @@ pi's steering and follow-up queues and per-tool `executionMode` are host-app sea
17
17
  equivalent and are not ported.
18
18
 
19
19
  ```ts
20
- import { brainWasm } from "@aexhq/brain";
20
+ import { brainEnv } from "@aexhq/brain";
21
21
  import { pi } from "@aexhq/agentloop-pi";
22
22
 
23
- const loopRuntime = brainWasm();
23
+ const loopRuntime = brainEnv({ name: "brain" });
24
24
  const session = await brain.sessions.create({
25
25
  agentloop: pi({ env: loopRuntime, contextWindow: 200_000 }),
26
26
  model,
@@ -32,8 +32,14 @@ The component is built by this package's publisher. Brain consumes the resulting
32
32
  does not compile its JavaScript source.
33
33
 
34
34
  The loop reads paginated session Events before each turn and saves its observation cursor in
35
- slots. Interrupted turns and environment failures enter the transcript as runtime observations.
35
+ kv. Interrupted turns and environment failures enter the transcript as runtime observations.
36
36
  A failed tool result goes back to the model with `is_error`; the loop does not retry it automatically.
37
- Brain can release execution between turns without losing this transcript or cursor. Physical
38
- browser and sandbox state follows the Environment provider's TTL and may be lost. The official
39
- `tool-env` inspection and lifecycle tool is planned after the MVP.
37
+ Brain can release execution between turns without losing this transcript or cursor. The caller
38
+ controls Environment lifetime; a browser closure or provider resource loss can still destroy its
39
+ physical state.
40
+
41
+ By default the model sees canonical Tool schemas. A Tool with multiple placements requires an
42
+ explicit `placements: { toolName: "environmentName" }` option. Set
43
+ `environmentSelection: "model"` to expose each Tool's authorized Environment names in its input
44
+ schema. The loop unwraps that choice before dispatch; Brain validates the selected pair and the
45
+ canonical input. Both modes use the same session Tool placements.
package/dist/index.mjs CHANGED
@@ -2,6 +2,8 @@
2
2
  import { agentloop, component } from "@aexhq/brain";
3
3
  import { z } from "zod";
4
4
  var options = z.object({
5
+ environmentSelection: z.enum(["hidden", "model"]).default("hidden"),
6
+ placements: z.record(z.string(), z.string()).default({}),
5
7
  contextWindow: z.number().int().positive().default(2e5),
6
8
  // pi defaults (compaction.ts): compact when context exceeds
7
9
  // contextWindow - reserveTokens, keep ~keepRecentTokens of recent messages.
Binary file
package/index.d.ts CHANGED
@@ -1,7 +1,9 @@
1
- import type { AgentloopBinding, Environment } from "@aexhq/brain";
1
+ import type { PlacedAgentloop, Environment } from "@aexhq/brain";
2
2
 
3
3
  export interface PiOptions {
4
4
  readonly env: Environment;
5
+ readonly environmentSelection?: "hidden" | "model";
6
+ readonly placements?: Readonly<Record<string, string>>;
5
7
  /** Model context window in tokens the compaction budget is measured against. Default 200000. */
6
8
  readonly contextWindow?: number;
7
9
  /** Compact when the estimated context exceeds contextWindow - reserveTokens. pi default 16384. */
@@ -12,4 +14,4 @@ export interface PiOptions {
12
14
  readonly compaction?: boolean;
13
15
  }
14
16
 
15
- export declare const pi: (options: PiOptions) => AgentloopBinding;
17
+ export declare const pi: (options: PiOptions) => PlacedAgentloop;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aexhq/agentloop-pi",
3
- "version": "2.1.0",
3
+ "version": "3.0.0",
4
4
  "description": "Pi-style parallel-Tool Brain extension",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -34,7 +34,7 @@
34
34
  "prepack": "npm run build"
35
35
  },
36
36
  "dependencies": {
37
- "@aexhq/brain": "0.17.0",
37
+ "@aexhq/brain": "0.18.0",
38
38
  "zod": "4.4.3"
39
39
  },
40
40
  "devDependencies": {}
package/src/component.mjs CHANGED
@@ -19,7 +19,7 @@ export async function turn(input) {
19
19
  const output = await runPi({
20
20
  input: JSON.parse(input.inputJson),
21
21
  transcript: JSON.parse(input.transcriptJson),
22
- slots: JSON.parse(input.slotsJson),
22
+ kv: JSON.parse(input.kvJson),
23
23
  events: JSON.parse(input.eventsJson),
24
24
  configuration: JSON.parse(input.configurationJson),
25
25
  system: input.system,
@@ -33,7 +33,7 @@ export async function turn(input) {
33
33
  });
34
34
  return {
35
35
  transcriptJson: JSON.stringify(output.transcript),
36
- slotsJson: JSON.stringify(output.slots),
36
+ kvJson: JSON.stringify(output.kv),
37
37
  resultJson: output.result === undefined ? undefined : JSON.stringify(output.result),
38
38
  };
39
39
  } catch (error) {
package/src/index.mjs CHANGED
@@ -1,6 +1,8 @@
1
1
  import { agentloop, component } from "@aexhq/brain";
2
2
  import { z } from "zod";
3
3
  const options = z.object({
4
+ environmentSelection: z.enum(["hidden", "model"]).default("hidden"),
5
+ placements: z.record(z.string(), z.string()).default({}),
4
6
  contextWindow: z.number().int().positive().default(200_000),
5
7
  // pi defaults (compaction.ts): compact when context exceeds
6
8
  // contextWindow - reserveTokens, keep ~keepRecentTokens of recent messages.
package/src/logic.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { toolPlacement } from "../../../shared/tool-placement.mjs";
1
2
  import { observeEvents } from "../../../shared/loop-events.mjs";
2
3
 
3
4
  const CHECKPOINT_PREFIX = "Context checkpoint from earlier in this conversation:\n\n";
@@ -59,9 +60,10 @@ export async function runPi(input, context) {
59
60
  compaction: true,
60
61
  ...input.configuration,
61
62
  };
63
+ const placement = toolPlacement(input.tools, options);
62
64
  const transcript = cloneJson(input.transcript);
63
- const observed_sequence = await observeEvents(context, transcript, input.slots.observed_sequence ?? 0);
64
- const saved = input.slots.checkpoint;
65
+ const observed_sequence = await observeEvents(context, transcript, input.kv.observed_sequence ?? 0);
66
+ const saved = input.kv.checkpoint;
65
67
  const checkpoint = saved === undefined ? { summary: null } : cloneJson(saved);
66
68
  const body = () => checkpoint.summary === null ? transcript : transcript.slice(1);
67
69
  const shouldCompact = () =>
@@ -86,6 +88,7 @@ export async function runPi(input, context) {
86
88
  const previous = checkpoint.summary === null ? "" : `Previous summary:\n\n${checkpoint.summary}\n\n`;
87
89
  const prompt = checkpoint.summary === null ? SUMMARIZATION_PROMPT : `${UPDATE_RULES}${SUMMARIZATION_PROMPT}`;
88
90
  const { message } = await context.model({
91
+ tools: [],
89
92
  messages: [{ role: "user", content: [{ type: "text", text: `${previous}${serializeConversation(messages.slice(0, cut))}\n\n${prompt}` }] }],
90
93
  });
91
94
  checkpoint.summary = text(message);
@@ -97,14 +100,14 @@ export async function runPi(input, context) {
97
100
  transcript.push({ role: "user", content: [{ type: "text", text: input.input.message }] });
98
101
  for (;;) {
99
102
  if (shouldCompact()) await compact();
100
- const { message, stop_reason } = await context.model({ messages: transcript });
103
+ const { message, stop_reason } = await context.model({ messages: transcript, tools: placement.definitions });
101
104
  transcript.push(message);
102
105
  const calls = message.content
103
106
  .filter((block) => block.type === "tool_use")
104
107
  .map((block) => ({ call_id: block.id, name: block.name, input: block.input }));
105
108
  if (calls.length === 0) {
106
109
  await context.emit("output_emitted", { type: "assistant_message", message: text(message) });
107
- return { transcript, slots: { checkpoint, observed_sequence } };
110
+ return { transcript, kv: { checkpoint, observed_sequence } };
108
111
  }
109
112
  if (stop_reason === "max_tokens") {
110
113
  transcript.push({
@@ -113,7 +116,7 @@ export async function runPi(input, context) {
113
116
  });
114
117
  continue;
115
118
  }
116
- const results = await context.dispatch(calls);
119
+ const results = await context.dispatch(calls.map(placement.invocation));
117
120
  const byCall = new Map(results.map((result) => [result.call_id, result]));
118
121
  transcript.push({
119
122
  role: "user",