Skip to content

Make the neutral core boundary real: src/core imports nothing from pi - #86

Merged
rgarcia merged 3 commits into
mainfrom
hypeship/neutral-core-boundary
Aug 15, 2026
Merged

Make the neutral core boundary real: src/core imports nothing from pi#86
rgarcia merged 3 commits into
mainfrom
hypeship/neutral-core-boundary

Conversation

@rgarcia

@rgarcia rgarcia commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 5d: make the neutral boundary real. src/core now imports nothing from pi — runtime or type-only — and an automated check enforces it. The . and ./pi export surfaces are unchanged in behavior: provider-qualified refs, tool identities, model-facing names, and catalog.model semantics (including object identity when nothing is derived) all work exactly as documented.

Neutral types in core

  • LoopToolDeclaration (core/tool-catalog.ts): { name, description, parameters: TSchema } replaces pi-ai's Tool in specs, catalog entries, and catalog.toolDeclarations. Structurally still a pi-ai Tool, so pi consumers are unaffected.
  • LoopExecutableTool / LoopToolExecutionResult (core/resources.ts): materialize(spec) returns { spec, execute(input, signal) } with neutral text/image content blocks. The pi wrapper shape (toolCallId, label, executionMode, AgentTool) lives in the pi tool manager's adapter, cached per executable so recompiles keep stable tool identity.
  • LoopCatalogModel / LoopModelFacts (core/model-info.ts): the compiler and menu see only { provider, id, api } plus binding-supplied availability facts (capabilities, nativeSurfaces) and model-preparation payload transforms passed as compile inputs. The pi binding derives facts from its registry/quirk tables (loopModelFacts()) and contributes the Anthropic thinking-budget transform (loopModelPreparationTransforms()); core only orders and validates what it is handed. This also removed core's provider-name payload branch.
  • LoopCompiledModel<M>: catalog.model is typed as the input model with api widened to string (compilation may derive a tool's required transport onto it), so the generic output type is honest without casts.

Schema builder decision

Core imports Type/TSchema from typebox@1.3.7 directly — the exact package and version pi-ai depends on and re-exports. npm dedupes to a single instance, so TSchema identity is unchanged for every consumer; parameters flow to the catalog exactly as before.

File moves

  • core/tool-manager.tssrc/pi/tool-manager.ts (joins compiled catalogs to executable pi AgentTools — genuinely pi-coupled).
  • pi/providers/anthropic/native.tscore/anthropic-native.ts: the pure input→canonical-action mapper is the execution half of core-declared specs, the same category as the OpenAI/Google mappers already in core/tools.ts. Stream/wire adapters all remain in src/pi/providers/.
  • modelSupportsDeferredToolspi/models.ts (interprets pi compat flags); Anthropic native version gates stay in pi/providers/anthropic/capabilities.ts; the published loop namespace is composed in pi/loop.ts, layering loop.providers.anthropic.supports over core's declarations.
  • Loop-owned api id constants (OPENAI_COMPUTER_USE_API, GOOGLE_INTERACTIONS_API) moved next to their consumer in core/tool-catalog.ts; transport routing stays in src/pi/providers.ts.
  • New src/pi/catalog.ts: ref-accepting compileLoopToolCatalog/loopToolMenu wrappers; explicit exports in src/index.ts shadow the core star exports so the . surface keeps accepting "provider:model" refs.

Enforcement

test/core-boundary.test.ts runs in the normal unit suite and walks every src/core/**/*.ts import/export-from/dynamic-import/require specifier with statement-anchored extraction. It default-denies: only core-relative paths and an allowlist of neutral bare imports (node:, typebox, @onkernel/sdk, sharp) pass. @earendil-works/*, @onkernel/loop, @onkernel/loop/pi, and any future alias fail, runtime or type-only. Verified red against probe files before landing.

Testing

  • npx tsc -b --force clean across the workspace.
  • npm test (loop): 33 files, 426 tests passed — the 420 at main plus new coverage for the boundary check, the AgentTool adapter seam, and the facts/preparation compile inputs.
  • Built package smoke-tested: string-ref compilation, menu availability (including native entries), and loop.providers.anthropic.supports.browser behave identically on dist.
  • Integration/live suites not run locally (they need a Kernel browser and provider keys); CI's integration job covers them.

Note

Medium Risk
Touches catalog compilation, tool materialization, and public export surfaces across core and pi; behavior is intended to be unchanged but the refactor spans critical agent-tool wiring.

Overview
Makes src/core a real framework-neutral layer by removing all pi / pi-ai imports and introducing neutral types the pi binding wraps at the edges.

Core now owns LoopToolDeclaration (schemas via typebox), LoopExecutableTool / LoopToolExecutionResult (execution is (input, signal) only), and LoopCatalogModel / LoopModelFacts so catalog compilation and the tool menu no longer resolve models or look up provider capabilities in core. The pi side supplies loopModelFacts(), loopModelPreparationTransforms() (e.g. Anthropic adaptive thinking), and ref-accepting compileLoopToolCatalog / loopToolMenu wrappers in pi/catalog.ts.

Moves pi-coupled code out of core: tool manager → pi/tool-manager.ts (with a cached AgentTool adapter), Anthropic native input mapping → core/anthropic-native.ts, modelSupportsDeferredToolspi/models.ts, and the published loop namespace gains providers.anthropic.supports from pi/loop.ts.

Adds test/core-boundary.test.ts to fail the unit suite if any src/core file imports outside an allowlist (no pi packages, no @onkernel/loop/pi). Public . behavior is preserved via explicit re-exports that shadow core for string model refs and composed loop.

Reviewed by Cursor Bugbot for commit 5a20110. Bugbot is set up for automated code reviews on this repo. Configure here.

Core now declares its own framework-neutral types and an automated check
enforces the boundary:

- LoopToolDeclaration replaces pi-ai's Tool in specs, catalog entries, and
  catalog.toolDeclarations (structurally still a pi-ai Tool).
- LoopExecutableTool replaces AgentTool as materialize()'s product: core
  executes (input, signal); the pi wrapper shape (toolCallId, label,
  executionMode) lives in the pi tool manager's AgentTool adapter.
- LoopCatalogModel replaces Model<Api> in the compiler, menu, and payload
  plans; compileLoopToolCatalog is generic over it so pi callers keep a
  Model-typed catalog.model.
- Core schemas import Type/TSchema from typebox directly (the same package
  and version pi-ai re-exports), instead of going through pi-ai.
- Model availability data moves to core/model-info.ts (native-surface and
  quirk tables, Anthropic version gates); registry-backed model resolution
  stays in src/pi/models.ts.
- The tool manager moves to src/pi (it joins catalogs to pi AgentTools);
  Anthropic native input mapping and adaptive thinking move to core (they
  were already pi-free); the loop-owned api ids move to the catalog module.
- The '.' surface keeps accepting provider-qualified model refs through
  pi-side compileLoopToolCatalog/loopToolMenu wrappers in src/pi/catalog.ts.
- test/core-boundary.test.ts fails the unit suite on any src/core import of
  @earendil-works/* or src/pi, runtime or type-only.
Review follow-ups on the neutral core boundary:

- Provider capability lookup returns to src/pi: the native-surface and quirk
  tables, their matchers, and computerUseNativeSurfaces/loopModelCapabilities
  live in pi/models.ts again. The compiler and menu consult LoopModelFacts
  ({capabilities, nativeSurfaces}) passed as a compile input, which the pi
  wrappers derive via loopModelFacts(); absent facts mean permissive
  capabilities and no native surfaces.
- The Anthropic adaptive-thinking payload hook returns to
  src/pi/providers/anthropic/. Core no longer hardwires a provider-name
  payload branch: compileLoopToolCatalog takes binding-supplied
  'model-preparation' transforms and only orders and validates them.
- The Anthropic native version gates move next to the native tool factories
  in core/tools.ts, which loop.providers.anthropic.supports.browser has
  always exposed from the '.' surface; pi's surface lookup imports them from
  there so the model tables carry no duplicate.
- The boundary test now allowlists core's bare imports (node:, typebox,
  @onkernel/sdk, sharp) and rejects everything else by default — including
  @onkernel/loop/pi and any future alias — with statement-anchored specifier
  extraction so template literals cannot false-positive, plus explicit cases
  covering the bypass.
- catalog.model is typed LoopCompiledModel<M> (api widened to string) instead
  of asserting the api swap stays an M, removing both 'as M' casts.
- catalog.model identity is preserved again when compilation derives nothing:
  facts ride beside the model rather than being spread onto a copy.

Tests: 426 passed, including new coverage for the preparation seam and the
boundary-check predicate.
…ding

Remaining availability interpretation leaves core:

- modelSupportsDeferredTools moves to pi/models.ts: it reads pi compat flags
  (supportsToolSearch, supportsToolReferences) and provider model families,
  which is the binding's knowledge. The '.' surface re-exports it from pi.
- The Anthropic native version gates return to
  pi/providers/anthropic/capabilities.ts. Core's tool namespace no longer
  carries an availability helper; the published loop namespace is composed in
  pi/loop.ts, which layers loop.providers.anthropic.supports over core's
  declarations. Core's compiler already consumes native-surface availability
  only through LoopModelFacts.
- LoopCatalogModel drops the now-unused compat field; the neutral view is
  identity plus transport.

Tests: 426 passed.
@rgarcia
rgarcia merged commit 3a20a81 into main Aug 15, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant