Make the neutral core boundary real: src/core imports nothing from pi - #86
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 5d: make the neutral boundary real.
src/corenow imports nothing from pi — runtime or type-only — and an automated check enforces it. The.and./piexport surfaces are unchanged in behavior: provider-qualified refs, tool identities, model-facing names, andcatalog.modelsemantics (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'sToolin specs, catalog entries, andcatalog.toolDeclarations. Structurally still a pi-aiTool, 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) andmodel-preparationpayload 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.modelis typed as the input model withapiwidened tostring(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/TSchemafromtypebox@1.3.7directly — the exact package and version pi-ai depends on and re-exports. npm dedupes to a single instance, soTSchemaidentity is unchanged for every consumer; parameters flow to the catalog exactly as before.File moves
core/tool-manager.ts→src/pi/tool-manager.ts(joins compiled catalogs to executable piAgentTools — genuinely pi-coupled).pi/providers/anthropic/native.ts→core/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 incore/tools.ts. Stream/wire adapters all remain insrc/pi/providers/.modelSupportsDeferredTools→pi/models.ts(interprets pi compat flags); Anthropic native version gates stay inpi/providers/anthropic/capabilities.ts; the publishedloopnamespace is composed inpi/loop.ts, layeringloop.providers.anthropic.supportsover core's declarations.OPENAI_COMPUTER_USE_API,GOOGLE_INTERACTIONS_API) moved next to their consumer incore/tool-catalog.ts; transport routing stays insrc/pi/providers.ts.src/pi/catalog.ts: ref-acceptingcompileLoopToolCatalog/loopToolMenuwrappers; explicit exports insrc/index.tsshadow the core star exports so the.surface keeps accepting"provider:model"refs.Enforcement
test/core-boundary.test.tsruns in the normal unit suite and walks everysrc/core/**/*.tsimport/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 --forceclean across the workspace.npm test(loop): 33 files, 426 tests passed — the 420 at main plus new coverage for the boundary check, theAgentTooladapter seam, and the facts/preparation compile inputs.loop.providers.anthropic.supports.browserbehave identically ondist.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/corea 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 viatypebox),LoopExecutableTool/LoopToolExecutionResult(execution is(input, signal)only), andLoopCatalogModel/LoopModelFactsso catalog compilation and the tool menu no longer resolve models or look up provider capabilities in core. The pi side suppliesloopModelFacts(),loopModelPreparationTransforms()(e.g. Anthropic adaptive thinking), and ref-acceptingcompileLoopToolCatalog/loopToolMenuwrappers inpi/catalog.ts.Moves pi-coupled code out of core: tool manager →
pi/tool-manager.ts(with a cachedAgentTooladapter), Anthropic native input mapping →core/anthropic-native.ts,modelSupportsDeferredTools→pi/models.ts, and the publishedloopnamespace gainsproviders.anthropic.supportsfrompi/loop.ts.Adds
test/core-boundary.test.tsto fail the unit suite if anysrc/corefile 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 composedloop.Reviewed by Cursor Bugbot for commit 5a20110. Bugbot is set up for automated code reviews on this repo. Configure here.