@aexhq/agentloop-pi 6.1.2 → 6.2.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
@@ -62,7 +62,7 @@ siblings. For hosted applications, publish bytes with Aex's attachment API and r
62
62
  Compaction explicitly resets the response format and installs a summary only after `end_turn`;
63
63
  a truncated, refused, or unknown summary leaves the original saved context intact.
64
64
 
65
- Version 6.1 targets Brain SDK 0.24 and preserves the 0.23 WIT and URL-media contract. Existing sessions keep their
65
+ Version 6.2 targets Brain SDK 0.25 and preserves the 0.23 WIT and URL-media contract. Existing sessions keep their
66
66
  immutable loop implementation; create new sessions to adopt the updated loop. Keep matching
67
67
  server/artifacts for recovery. An upgrade does not migrate or delete session data.
68
68
 
@@ -81,3 +81,17 @@ without rebuilding Wasm. Run `npm test -w packages/loop-pi` for the Component bu
81
81
  tests, then the compiled journeys described in the repository README. All release gates remain
82
82
  required. A rebuilt Component needs a new admission and session. Package source is not a public
83
83
  logic-library entry point, and existing sessions keep their admitted implementation.
84
+
85
+ ## Hosted structured output
86
+
87
+ Set `output: { schema: jsonSchema, maxCorrections: 2 }` on the loop to validate JSON Schema
88
+ 2020-12 inside the hosted turn. `z.toJSONSchema(schema)` supplies a schema from Zod. Schemas
89
+ compile strictly before model effects: unknown keywords, unresolved external references and
90
+ unsupported patterns fail explicitly. Standard formats are validated without coercion.
91
+
92
+ Corrections run without tools and retain previous results. They finish in the same turn when
93
+ using `session.submit()`, without a client process driving extra turns. Only a valid final
94
+ answer emits `assistant_message`; its parsed value is the turn result. Refusal, truncation,
95
+ provider failure and cancellation do not trigger formatting retries. Corrections default to
96
+ two, with a supported range of zero through ten. Application business rules still belong in
97
+ its tools or persistence boundary.
package/dist/index.mjs CHANGED
@@ -9,7 +9,11 @@ var options = z.object({
9
9
  // contextWindow - reserveTokens, keep ~keepRecentTokens of recent messages.
10
10
  reserveTokens: z.number().int().positive().default(16384),
11
11
  keepRecentTokens: z.number().int().positive().default(2e4),
12
- compaction: z.boolean().default(true)
12
+ compaction: z.boolean().default(true),
13
+ output: z.strictObject({
14
+ schema: z.union([z.boolean(), z.record(z.string(), z.unknown())]),
15
+ maxCorrections: z.number().int().min(0).max(10).default(2)
16
+ }).optional()
13
17
  }).strict();
14
18
  var pi = agentloop({
15
19
  options,
Binary file
package/index.d.ts CHANGED
@@ -12,6 +12,8 @@ export interface PiOptions {
12
12
  readonly keepRecentTokens?: number;
13
13
  /** Disable automatic compaction entirely. Default true (enabled). */
14
14
  readonly compaction?: boolean;
15
+ /** Hosted JSON Schema 2020-12 validation. Corrections run without tools in the same turn. */
16
+ readonly output?: { readonly schema: boolean | Readonly<Record<string, unknown>>; readonly maxCorrections?: number };
15
17
  }
16
18
 
17
19
  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": "6.1.2",
3
+ "version": "6.2.0",
4
4
  "description": "Pi-style parallel-Tool Brain extension",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,7 +19,10 @@
19
19
  "tag": "next"
20
20
  },
21
21
  "exports": {
22
- ".": { "types": "./index.d.ts", "import": "./dist/index.mjs" },
22
+ ".": {
23
+ "types": "./index.d.ts",
24
+ "import": "./dist/index.mjs"
25
+ },
23
26
  "./package.json": "./package.json"
24
27
  },
25
28
  "files": [
@@ -37,8 +40,9 @@
37
40
  "prepack": "npm run build"
38
41
  },
39
42
  "dependencies": {
40
- "@aexhq/brain": "0.24.3",
43
+ "@aexhq/brain": "0.25.1",
44
+ "ajv": "8.20.0",
45
+ "ajv-formats": "3.0.1",
41
46
  "zod": "4.4.3"
42
- },
43
- "devDependencies": {}
47
+ }
44
48
  }
package/src/index.mjs CHANGED
@@ -9,6 +9,8 @@ const options = z.object({
9
9
  reserveTokens: z.number().int().positive().default(16_384),
10
10
  keepRecentTokens: z.number().int().positive().default(20_000),
11
11
  compaction: z.boolean().default(true),
12
+ output: z.strictObject({ schema: z.union([z.boolean(), z.record(z.string(), z.unknown())]),
13
+ maxCorrections: z.number().int().min(0).max(10).default(2) }).optional(),
12
14
  }).strict();
13
15
 
14
16
  export const pi = agentloop({
package/src/logic.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { toolPlacement } from "../../../shared/tool-placement.mjs";
2
2
  import { observeEvents } from "../../../shared/loop-events.mjs";
3
3
  import { toolResult } from "../../../shared/tool-output.mjs";
4
+ import { structuredOutput } from "../../../shared/structured-output.mjs";
4
5
 
5
6
  const CHECKPOINT_PREFIX = "Context checkpoint from earlier in this conversation:\n\n";
6
7
 
@@ -64,6 +65,7 @@ export async function runPi(input, context) {
64
65
  ...input.configuration,
65
66
  };
66
67
  const placement = toolPlacement(input.tools, options);
68
+ const output = structuredOutput(options.output);
67
69
  const transcript = cloneJson(input.transcript);
68
70
  const observed_sequence = await observeEvents(context, transcript, (await context.kv.read("observed_sequence")) ?? 0);
69
71
  const saved = await context.kv.read("checkpoint");
@@ -111,7 +113,7 @@ export async function runPi(input, context) {
111
113
  await context.setTranscript(transcript);
112
114
  await context.kv.put("checkpoint", checkpoint);
113
115
  }
114
- const { message, stop_reason } = await context.model({ messages: transcript, tools: placement.definitions });
116
+ const { message, stop_reason } = await context.model(output?.request(transcript, placement.definitions) ?? { messages: transcript, tools: placement.definitions });
115
117
  transcript.push(message);
116
118
  await context.setTranscript(transcript);
117
119
  await context.kv.put("checkpoint", checkpoint);
@@ -119,9 +121,16 @@ export async function runPi(input, context) {
119
121
  .filter((block) => block.type === "tool_use")
120
122
  .map((block) => ({ call_id: block.id, name: block.name, input: block.input }));
121
123
  if (calls.length === 0) {
124
+ const final = output?.accept(text(message), stop_reason);
125
+ if (final?.correction) {
126
+ transcript.push(final.correction);
127
+ await context.setTranscript(transcript);
128
+ continue;
129
+ }
122
130
  await context.emit("output_emitted", { type: "assistant_message", message: text(message) });
123
- return {};
131
+ return final ? { result: final.value } : {};
124
132
  }
133
+ if (output?.correcting) output.rejectTools();
125
134
  if (stop_reason === "max_tokens") {
126
135
  transcript.push({
127
136
  role: "user",