Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
bf68787
Cut Mission 7c as the live Inventory authority.
lunelson Sep 11, 2026
bd5afaa
Mount compiler feedback on ordinary batched construction.
lunelson Sep 11, 2026
33186e0
Adopt the mutations/handle execute contract for code-bearing operations
lunelson Sep 11, 2026
f15bb23
Record ELK layout as a separate browser command with observed pre/posโ€ฆ
lunelson Sep 11, 2026
b36900b
Settle the ELK fog-line in Mission 7c and list applyAutoLayout among โ€ฆ
lunelson Sep 11, 2026
69069a8
Wrap the compiler-feedback browser proof as a vitest integration and โ€ฆ
lunelson Sep 11, 2026
9646bb0
Tell mutate_petrinet callers to order definitions before the nodes thโ€ฆ
lunelson Sep 11, 2026
9268482
Prove dependency changes invalidate untouched SDCPN code
lunelson Sep 11, 2026
ab3dd51
Expose headless SDCPN TypeScript diagnostics from petrinaut-core
lunelson Sep 11, 2026
b0bfb3d
Freeze the Inventory-derived code-bearing slice on the batched carrier
lunelson Sep 11, 2026
f276709
Route ordinary why questions through getLatestNetDefinition then brunโ€ฆ
lunelson Sep 11, 2026
279e697
Record Mission 7c proof-floor status and the why-routing live witness
lunelson Sep 11, 2026
4904f01
Admit edits to existing net parts in the mutate_petrinet batch
lunelson Sep 11, 2026
f1b9671
Reduce MISSION.next.md to future territory and re-point draft links
lunelson Sep 11, 2026
50dfa22
Admit net-level removals and record the mutation capability matrix
lunelson Sep 11, 2026
308cb24
Reconcile Mission 7c against the owner's reconciliation report
lunelson Sep 11, 2026
54fbc20
Report pending diagnostics on timeout and prove referenced removals cโ€ฆ
lunelson Sep 11, 2026
b4ad559
Make Brunch the default assistant with the stock assistant as a selecโ€ฆ
lunelson Sep 11, 2026
13d4e7a
Fold Flue history into one net ledger, with freshness as its first coโ€ฆ
lunelson Sep 11, 2026
e4a7bfd
Apply the second reconciliation review to the Mission 7c contract
lunelson Sep 11, 2026
127e223
Correct the ownership and current state of the Inventory copy path
lunelson Sep 11, 2026
b2ec6b1
State crew reservation as a legacy Mission 6 resume fixture, regressiโ€ฆ
lunelson Sep 11, 2026
90e5c83
Separate the Mission 7c contract from implementation evidence
lunelson Sep 11, 2026
c4baea6
Remove transient Mission 7c planning surfaces after freshness merge
lunelson Sep 11, 2026
fc0789a
Define seeded fixture bundles as the Inventory delivery path
lunelson Sep 11, 2026
6e2d62a
Verify ledger mutation events against complete batch output
lunelson Sep 11, 2026
f81d53c
Guard reusable SDCPN guidance against Inventory fixture leakage
lunelson Sep 11, 2026
a8985c2
Pin the provisional mutation batch boundary at 30 operations
lunelson Sep 11, 2026
b2fa082
Prove native carriage of the full mutate_petrinet schema
lunelson Sep 11, 2026
5e9822e
Probe seeded worked-model copy semantics in memory
lunelson Sep 11, 2026
fc13f53
Add the principal-owned worked-model copy HTTP contract
lunelson Sep 11, 2026
821bd2d
Add a distinct worked-model bundle route identity
lunelson Sep 11, 2026
46cbeca
Persist build-seeded worked-model copies in Postgres
lunelson Sep 11, 2026
8b57c73
Add the browser client for worked-model copies
lunelson Sep 11, 2026
a882ff9
Open and persist principal-owned worked-model copies in Petrinaut
lunelson Sep 11, 2026
70b6277
Package local persona runs as versioned worked-model fixtures
lunelson Sep 11, 2026
a1f3790
Discover and validate build-packaged worked-model fixtures
lunelson Sep 11, 2026
3d9e7c7
Proxy worked-model API requests in browser integration fixtures
lunelson Sep 11, 2026
6489a80
Settle the Inventory reference and explanation ownership boundaries
lunelson Sep 11, 2026
1ebb347
Add the established Inventory reference and persona context
lunelson Sep 11, 2026
b07253c
Allow Pi persona runs to preload a reference Petrinaut net
lunelson Sep 11, 2026
323e3f7
Prove the seeded worked-model copy path through the browser
lunelson Sep 11, 2026
ef5afc0
Retain the readable Inventory source beside its persona pack
lunelson Sep 11, 2026
2411da8
Check the ordinary Brunch tool catalogue against provider mounts
lunelson Sep 11, 2026
f45d805
Settle workpiece tool names and expose remaining query fog
lunelson Sep 11, 2026
73bae77
Rename workpiece operations and record full-revision deltas
lunelson Sep 11, 2026
bd8ecc3
Settle query_workpiece as the current-basis operation
lunelson Sep 11, 2026
8e8253f
Give every Petrinaut document change a stable revision identity
lunelson Sep 11, 2026
a0ac647
Replace why tool with workpiece query
lunelson Sep 11, 2026
68a34c1
Persist Petrinaut revisions through worked models
lunelson Sep 11, 2026
03efa5a
Rename Brunch Petrinaut tool family
lunelson Sep 11, 2026
2c834f4
Keep the worked-model fixture complete while reducing branching accepโ€ฆ
lunelson Sep 13, 2026
fabec89
Index the browser document revision before Flue admits the submission
lunelson Sep 13, 2026
a26586a
Replace Petrinaut tool aliases with host-owned Brunch tools
lunelson Sep 13, 2026
09a1d24
Narrow Petrinaut Core exports and centralize the Brunch client-tool cโ€ฆ
lunelson Sep 13, 2026
5fe7613
Fix worked-model assistant authority
lunelson Sep 13, 2026
1200670
Authorize the Brunch test-value remediation side quest
lunelson Sep 13, 2026
e5b25ad
Make Petrinaut titles read-only without a setter
lunelson Sep 13, 2026
dfe92fc
Add storage-neutral document lifecycle repositories
lunelson Sep 13, 2026
0f3973d
Resolve Brunch and Petrinaut website lint findings
lunelson Sep 13, 2026
3446ecf
Synchronize Mission 7c document lifecycle guidance
lunelson Sep 13, 2026
c130e99
side quest update
lunelson Sep 13, 2026
a44d618
Make Petrinaut React resources commit-safe
lunelson Sep 13, 2026
5f8f16d
Keep Petrinaut website render paths pure
lunelson Sep 13, 2026
059c8fb
Record persisted-state library review gate
lunelson Sep 13, 2026
862043c
Restore complete worked-model bundle authority
lunelson Sep 13, 2026
c7c99c6
Name incomplete worked-model net projection plumbing
lunelson Sep 13, 2026
11a607d
Clarify incomplete net projection UI and docs
lunelson Sep 13, 2026
258fdb4
Make Brunch tests pay for regressions
lunelson Sep 13, 2026
e7fac91
Align Brunch test-writing guidance with oracle ownership
lunelson Sep 13, 2026
f773a0b
Keep live documents ahead of delayed acknowledgements and conflictingโ€ฆ
lunelson Sep 13, 2026
9818b74
reflow of mission docs
lunelson Sep 13, 2026
5af3443
Give worked-model net projections one plugin-owned wire contract.
lunelson Sep 13, 2026
8043fe7
Restore Brunch mission progress tracking and clarify document ownership
lunelson Sep 13, 2026
34a1b21
Narrow Mission 7c to a from-scratch persona worked example
lunelson Sep 13, 2026
e44cf51
Align Inventory persona inputs with from-scratch construction
lunelson Sep 13, 2026
b475f86
Authorize the browser-executed persona throughline proof
lunelson Sep 13, 2026
5f4ba50
Execute persona turns through the real browser panel
lunelson Sep 13, 2026
2c2bd1c
Record the shared Sonnet persona run allocation
lunelson Sep 13, 2026
72a8e8b
Connect shared persona accounting and pause for recording
lunelson Sep 13, 2026
4a1a228
Record live persona connection and initial workpiece failure
lunelson Sep 13, 2026
4dc857a
Preserve valid nullable tool arguments before coercion
lunelson Sep 13, 2026
3e6627c
Authorize same-run persona recovery with the retained usage hold
lunelson Sep 13, 2026
5c6e416
Repair persona resume, response streaming and model-facing tool schemas
lunelson Sep 14, 2026
345ec4c
Canonicalize browser-visible persona testing
lunelson Sep 14, 2026
e6a3e3f
Authorize the bounded fresh Sonaflozin observation
lunelson Sep 14, 2026
9971724
Validate native Brunch tool schemas with Anthropic before paid runs
lunelson Sep 14, 2026
b5657c2
Retain failed-opening hold for the Sonaflozin retry
lunelson Sep 14, 2026
b66327c
Record the relaunched Sonaflozin recording pause
lunelson Sep 14, 2026
dd251ee
Retire persona budget enforcement after the recording stop
lunelson Sep 14, 2026
5c1bcd4
Remove accounting cutoffs from browser-visible persona runs
lunelson Sep 14, 2026
9da88bf
Build supported net fragments alongside elicitation
lunelson Sep 14, 2026
cfc4f22
Fix assistant diagnostics completion and show tool progress
lunelson Sep 14, 2026
0efff9a
Close Mission 7c provisionally and defer example completion
lunelson Sep 14, 2026
0b015ee
Preserve unknown net mutations as unrecorded
lunelson Sep 14, 2026
7bb1122
Preserve local documents across source transitions
lunelson Sep 14, 2026
4abfb41
Complete the Brunch browser CORS envelope
lunelson Sep 14, 2026
e9612e8
Respect host title editing capability
lunelson Sep 14, 2026
7d5e5ca
Consolidate Petrinaut release notes
lunelson Sep 14, 2026
ce50d50
Fix clean-checkout CI regressions
lunelson Sep 14, 2026
45eb24a
Align Brunch integration checks with renamed contracts
lunelson Sep 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/brunch-petrinaut-core-contracts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut-core": patch
---

Expose stable document revision identities and explicit browser and headless diagnostics for captured definitions, expand the selected mutation batch with supported update and removal operations, and start document observation only when subscribed.
5 changes: 5 additions & 0 deletions .changeset/brunch-petrinaut-host-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@hashintel/petrinaut": patch
---

Expose revision identity, editor commands, current-definition diagnostics, and user-guide content to host-owned assistant tools, while showing unfinished tool progress and refusing unavailable title edits. Keep host transport and store resources current across route changes, with stable React subscriptions and capability-based read-only titles.
18 changes: 17 additions & 1 deletion .yarn/patches/@earendil-works-pi-ai-npm-0.83.0-c607801251.patch
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ index ed4e4a545f30a40b8da8ac7ffd5cdc427f562e6b..3463a097ad3c15c2d2ba4dd6ea120b8a

### Constrained Sampling for Tools

+Brunch overrides Pi's Anthropic `convertTools` collapse to `{ type, properties, required }`. That collapse is deliberate for typical Pi tools and remains upstream through 0.85, including when `strict` is off. Brunch construction tools publish nested JSON Schema (`$defs`, `additionalProperties`, descriptions, joined `brunch` fields) that native admission and the model must see on the wire; constrained-sampling `strict` cannot be the carriage switch, because unsupported models would silently strip again. This patch is pinned to 0.83.0 and applies only to Anthropic `input_schema` serialization. It does not change validation, other providers, or tool execution. Drop or rewrite it when upgrading `pi-ai` if upstream grows an explicit full-schema carriage option.
+Brunch preserves Anthropic `input_schema` independently of constrained sampling; Pi's deliberate `{ type, properties, required }` collapse would discard construction schema fields. Drop that change when upstream supports full-schema carriage. This 0.83.0 patch also backports upstream 2e95584dab802ae2f7c8d1a4994d6e0e9f67ec09: preserve values already valid in an anyOf/oneOf arm before attempting coercion. Without it a valid first-workpiece baseRevisionId null becomes an empty string. Remove that backport when upgrading to a version containing the fix; retain ordinary validation and coercion of inputs that match no arm.
+
Tools can opt in to provider-side constrained sampling. For JSON-schema tools, `strict: 'prefer'` uses provider-side strict schema enforcement when supported and otherwise falls back to normal tool calling. `strict: 'require'` fails the request when the active provider/model cannot honor it. Set `constrainedSampling: false` to explicitly opt out; it behaves the same as omitting the field.

Expand Down Expand Up @@ -36,3 +36,19 @@ index 17ec1ac38f1124cbb520522660b6f0dcab70d64d..692f81e97038bd8f2cab749d5a43fcc8
return {
name: isOAuthToken ? toClaudeCodeName(tool.name) : tool.name,
description: tool.description,
diff --git a/dist/utils/validation.js b/dist/utils/validation.js
--- a/dist/utils/validation.js
+++ b/dist/utils/validation.js
@@ -152,6 +152,12 @@ function applySchemaArrayCoercion(value, schema) {
}
function coerceWithUnionSchema(value, schemas) {
for (const schema of schemas) {
+ const validator = getSubSchemaValidator(schema);
+ if (validator?.Check(value)) {
+ return value;
+ }
+ }
+ for (const schema of schemas) {
const candidate = structuredClone(value);
const coerced = coerceWithJsonSchema(candidate, schema);
const validator = getSubSchemaValidator(schema);
195 changes: 32 additions & 163 deletions apps/brunch-agent/.pi/extensions/brunch-persona-testing.ts
Original file line number Diff line number Diff line change
@@ -1,191 +1,60 @@
import { readBrowserSessionOptions } from "../../src/evaluations/persona/browser-session.ts";
/**
* Pi extension entry for the Brunch persona harness.
*
* Load it from `apps/brunch-agent` with
* `--extension .pi/extensions/brunch-persona-testing.ts`; the persona policy
* and operating instructions sit in the same-named folder beside this file.
* This entry owns only Pi registration and flag handling. The tool lives in
* `src/evaluations/persona/brunch-turn.ts` and the client-tool hosts in
* `src/evaluations/persona/client-tool-hosts.ts`, where the application's
* lint, type-check, and unit tests govern them.
*/
/** Pi registration for the browser-visible persona launcher; no independent SDK transport. */
import { sendPersonaBrowserTurn } from "../../src/evaluations/persona/browser-bridge.ts";
import {
type BrunchTurnExtensionApi,
registerBrunchTurn,
requireConversationId,
createBrunchTurnTool,
} from "../../src/evaluations/persona/brunch-turn.ts";
import {
type BrunchClientToolHost,
createMockClientToolHost,
createRealHeadlessClientToolHost,
readMockCalls,
TOOL_HOST_FLAG,
} from "../../src/evaluations/persona/client-tool-hosts.ts";
import { writeProofArtifacts } from "../../src/evaluations/persona/proof-artifacts.ts";
import {
registerPersonaAccounting,
type PersonaAccountingContext,
} from "../../src/evaluations/persona/request-accounting.ts";

import type { Provider } from "@earendil-works/pi-ai";

/** The slice of Pi's extension API this entry needs; Pi itself is not a workspace dependency. */
interface BrunchPersonaExtensionApi extends BrunchTurnExtensionApi {
registerProvider(provider: Provider): void;
registerFlag(
name: string,
options: {
readonly description?: string;
readonly type: "string";
readonly default?: string;
},
options: { readonly description: string; readonly type: "string" },
): void;
getFlag(name: string): boolean | string | undefined;
on(
event: "session_start" | "session_shutdown",
handler: (
event: unknown,
context: PersonaAccountingContext,
) => void | Promise<void>,
handler: () => void | Promise<void>,
): void;
}

const TOOL_MOCKS_FLAG = "brunch-tool-mocks";
const HEADLESS_TITLE_FLAG = "brunch-headless-title";
const EVIDENCE_DIRECTORY_FLAG = "brunch-evidence-dir";
const BROWSER_SESSION_FLAG = "brunch-browser-session";

const stringFlag = (
pi: BrunchPersonaExtensionApi,
name: string,
): string | undefined => {
const value = pi.getFlag(name);
return typeof value === "string" && value.trim().length > 0
? value.trim()
: undefined;
};

const createConfiguredClientToolHost = (
pi: BrunchPersonaExtensionApi,
): BrunchClientToolHost | undefined => {
const mode = stringFlag(pi, TOOL_HOST_FLAG) ?? "none";
if (mode === "none") return undefined;

if (mode === "mock") {
const fixturePath = stringFlag(pi, TOOL_MOCKS_FLAG);
if (fixturePath === undefined) {
throw new Error(
`--${TOOL_MOCKS_FLAG} is required when --${TOOL_HOST_FLAG}=mock`,
);
}
return createMockClientToolHost(readMockCalls(fixturePath));
}

if (mode === "real-headless") {
const title =
stringFlag(pi, HEADLESS_TITLE_FLAG) ??
`Brunch persona ${requireConversationId(process.env["PI_SUBAGENT_NAME"])}`;
return createRealHeadlessClientToolHost(title);
}

throw new Error(
`--${TOOL_HOST_FLAG} must be one of none, mock, or real-headless; received ${mode}`,
);
};
const browserBridgeFlag = "brunch-browser-bridge";

// Pi loads an extension through its default export.
export default async function brunchPersonaTestingExtension(
export default function brunchPersonaTestingExtension(
pi: BrunchPersonaExtensionApi,
): Promise<void> {
registerPersonaAccounting(pi);
pi.registerFlag(TOOL_HOST_FLAG, {
type: "string",
default: "none",
description: "Client-tool host: none, mock, or real-headless",
});
pi.registerFlag(TOOL_MOCKS_FLAG, {
type: "string",
description: "Ordered JSON fixture used by the mock client-tool host",
});
pi.registerFlag(HEADLESS_TITLE_FLAG, {
type: "string",
description: "Document title used by the real-headless Petrinaut host",
});
pi.registerFlag(EVIDENCE_DIRECTORY_FLAG, {
type: "string",
description:
"Directory for canonical snapshot, transcript, and trace files",
});

pi.registerFlag(BROWSER_SESSION_FLAG, {
): void {
pi.registerFlag(browserBridgeFlag, {
type: "string",
description:
"Private operator JSON captured from an initialized Petrinaut browser session",
"Private launcher socket for turns executed by the real browser panel",
});
let clientToolHost: BrunchClientToolHost | undefined;
let generation = 0;
const dispose = async () => {
pi.on("session_shutdown", () => {
generation += 1;
});
pi.on("session_start", () => {
// Pi hydrates CLI flags after the factory, and may continue after lifecycle errors.
// Invalidate prior closures before validation so failure cannot retain an old route.
generation += 1;
const previousHost = clientToolHost;
clientToolHost = undefined;
await previousHost?.dispose?.();
};

pi.on("session_shutdown", dispose);
pi.on("session_start", async () => {
// CLI extension flags are applied only after the factory has finished.
// Invalidate old tool closures before any fallible cleanup or validation:
// Pi reports lifecycle errors but may continue running the agent.
await dispose();
const currentGeneration = generation;
const browserSessionPath = stringFlag(pi, BROWSER_SESSION_FLAG);
if (
pi.getFlag(BROWSER_SESSION_FLAG) !== undefined &&
browserSessionPath === undefined
) {
throw new Error("--brunch-browser-session requires a non-empty path");
}
if (
browserSessionPath !== undefined &&
(stringFlag(pi, TOOL_HOST_FLAG) ?? "none") !== "none"
) {
const value = pi.getFlag(browserBridgeFlag);
const socketPath = typeof value === "string" ? value.trim() : "";
if (!socketPath)
throw new Error(
"Browser attachment requires --brunch-tool-host=none; browser mutation hosting is not implemented",
"Persona requires the browser launcher. Run yarn brunch:persona --case <name-or-directory>; it supplies --brunch-browser-bridge.",
);
}
const browserOptions =
browserSessionPath === undefined
? undefined
: await readBrowserSessionOptions(browserSessionPath);

clientToolHost = createConfiguredClientToolHost(pi);

registerBrunchTurn(
{
registerTool: (tool) =>
pi.registerTool({
...tool,
execute: async (...args) => {
if (generation !== currentGeneration) {
throw new Error(
"brunch_turn session is not initialized; attachment unavailable",
);
}
return tool.execute(...args);
},
}),
},
{
...browserOptions,
resolveClientToolHost: () => clientToolHost,
retainSnapshot: async (snapshot) => {
const directory = stringFlag(pi, EVIDENCE_DIRECTORY_FLAG);
if (directory !== undefined) {
await writeProofArtifacts(directory, snapshot);
}
},
},
const tool = createBrunchTurnTool((message, signal) =>
sendPersonaBrowserTurn(socketPath, message, signal),
);
pi.registerTool({
...tool,
execute: async (...args) => {
if (generation !== currentGeneration)
throw new Error(
"brunch_turn session is not initialized; browser bridge unavailable",
);
return tool.execute(...args);
},
});
});
}
Loading
Loading