npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@neuralis/package-system

v0.3.1

Published

The foundational contract package for Neuralis — the text of the package protocol itself. Everything the product vision promises ("every capability is a package", "a universal AI package consumer") is ultimately a type, a validator, or a dispatcher define

Readme

@neuralis/package-system

The foundational contract package for Neuralis — the text of the package protocol itself. Everything the product vision promises ("every capability is a package", "a universal AI package consumer") is ultimately a type, a validator, or a dispatcher defined here. It defines PackageDefinition, the contribution vocabulary every other package speaks (PackageTool, PackageFile, PackageConnector, PackagePolicy, PackageUriPolicies, UiElement, …), the lifecycle hooks the host calls, the connector port a filesystem-like source must implement, the URI policy evaluator that gates every read/write/exec, the runtime-stack snapshot every package and the system prompt reads from, the manifest/contribution validators, the result-bounding helper, and the in-process registries / dispatchers / session store used by the host runtime.

This package has no LLM-facing surface. No tools, skills, files, UI, or team members ship from here. Every other package depends on it; it depends on nothing inside Neuralis. Treat its public exports as the stable contract vocabulary of the platform — breaking changes here are a breaking change for every consumer.

Module format. ESM is the convention for a first-party package — this one included: "type": "module" in package.json, module/moduleResolution: "NodeNext" and target: "ES2022" in tsconfig.json, and explicit .js extensions on all relative imports (a directory import resolves to /index.js). It is a convention, not a requirement: the loader performs the standard CommonJS default-export interop, so a package compiled to CJS loads — one first-party package is built that way today. Prefer ESM anyway; under CJS a route's named exports (pattern, feature) reach the module namespace only through Node's CommonJS lexer, which does not see dynamically-assigned exports. Consume the subpath exports below by their published specifiers; the runtime resolves them through the package exports map. The Next.js host bundles its own client/server code with moduleResolution: "bundler" and consumes these ESM packages directly.

Subpath exports

Most consumers should target the narrowest entry point. The root barrel is a convenience re-export of the server-side kernel (contracts, registries, binding, runtime, access, policies, source) — it is deliberately NOT a superset: paths, testing, web-common and client are absent from it entirely, and of validation and data it carries only part of each subpath's surface. Import by subpath and the resolution is unambiguous.

| Entry | What lives here | |---|---| | @neuralis/package-system | Convenience: re-exports of every subpath below | | .../contracts | All TypeScript types — PackageDefinition, contributions, lifecycle, sessions, connector port, result bound; ProjectServiceFactory.getExisting observes cached/pending services without building or touching LRU — plus the kernelTunables seam (setKernelTunablesSource/kernelTunable): host-injected LIVE knobs for kernel operational defaults, typed named fields only (the kernel never learns platform-config key names; agent-core declares the backing keys). Absent/throwing source ⇒ undefined ⇒ hardcoded defaults, so standalone consumers (neuralweb) work with no host wired. Also the operator-endpoint contract (endpoints.ts, see Operator endpoints below) and the outbound header NAME/VALUE rules it shares with remote MCP assignments (outboundHeaders.ts) | | .../paths | resolveNeuralisHome, resolveProjectRoot, resolveProjectDataRoot, resolvePackageDataDir, isPathInside, NeuralisRoots; discoverRuntimeStack, discoverOsMounts, realProjectRoot, resolveHostPath, resolveContainerPath, protectedRootPaths, RuntimeStackInfo, OsMount | | .../policies | URI policy evaluator (evaluateUriPolicy, evaluateSourceLevelUriPolicy, classifyUriSubtree, evaluateUriPolicyFloor, resolveRoleLayer, assertUriCapability, mergeBaselines, …) + UriPolicyDeniedError. The ONE evaluator — brain-core's permissionResolver is a delegating adapter over these, not a second implementation | | .../validation | validateManifest, validateContribution, validateToolSchema, validateXNeuralis (+ getUnknownXNeuralisKeys), the shared validateRequiresShape, validateWorkflowTemplate + the reusable AJV fragments from workflowSchemas (workflowScheduleConfigSchema, workflowTriggerSchema, workflowExecutionPolicySchema, workflowInstructionTemplateSchema, sessionSetupSchema), and the canonical surface-asset layout rule (resolveSurfaceAssetEntry, canonicalSurfaceAssetRoot, isSafeSurfaceIdSegment, isAbsoluteAssetUrl, normalizeRelativeAssetUrl, isHtmlEntryPath, SURFACE_ASSET_ROOT_PREFIX, SHARED_ASSET_DIR) | | .../tool-name | TOOL_NAME_PATTERN / isWireSafeToolName: the ONE zero-import tool-name predicate, safe in both server and browser graphs. The validation barrel reaches Node filesystem code and the client barrel reaches React hooks; mixed consumers use this direct leaf export. | | .../access | hasFeature, requireFeature, meetsRequires (the ONE shared requires.features visibility predicate — S2), rolePriority / canAssignRole / validateRoleAssignment / isValidRolePriority (the S4 ordinal role-priority gate — lower = stronger, declared range 1..99), isPlatformTierFeature (the platform.* tier LABEL — never a gate), the catalog collectors collectAllFeatures / collectFeatureMetadata / mergeDefaultRoleGrants, the derived buildFeatureConsumersIndex / summarizeConsumers (which contributions a feature id gates — computed, never stored on a manifest), PackageFamilyResolver, PackageAccessCatalog. See src/access/README.md | | .../paths | Runtime-stack + home resolution, and containment.ts — the ONE path-containment primitive (resolveContained/…Sync/…Lexical, containedOrThrow, openContainedForRead/ForWrite/openContainedHandle, listContained/…Sync, the derivations withSuffix/sibling/child/parentOf/ensureDir/ensureDirSync, the guards assertPathSegment/assertPathSegments/isSafePathSegment/isSafePathSegments, the branded ContainedPath + ContainmentDenial). See Path containment below | | .../data | FileStore, JsonlAppender, PackageLogger — file-backed primitives — the crash-durable write primitive (durableReplaceFile & co.), the data-format ledger (declareDataFormats/claimDeclared/claimDataFormat/assertRecordFormat) and checkpoints (createCheckpoint/listCheckpoints/restoreCheckpoint/pruneCheckpoints), plus FsWatchHub (D6): the ONE shared, ref-counted chokidar-v5 filesystem-event substrate. Full contract under Data primitives below (one watcher per absolute root, coalescing, ignored push-down, classified overflow, bounded reconnect); the hub knows ONLY absolute OS paths — URI mapping, exclude semantics, trust and scoping are the consumer's job (brain-core subscribeEvents today; packages:// hot-reload later) | | .../source | Generic source vocabulary — the slug helpers (parseSource, normalizeSource, deriveSourceKind, validateSource, resolveLocalMounts) and the sourceConfig types + validateSourceConfig (SourceConfig, the SourceConnection union, SyncConfig/SyncTrigger/SyncStatus, HierarchicalPermissions) — plus packageLayout — the ONE shared package-layout rules module: the trigger-category table (categorizeContributionPath), the source-package grouping rules (detectPackageRoots), the marker dot-dir set (ALLOWED_DOT_DIRS), identity-file recognition, skill-bundle ordering/caps, the minimal frontmatter parser (a - item list indented or at its key's own indent, [a, b] flow lists; a # comment in a list position is dropped through stripYamlComment, which agent-core's activation parser shares, while a plain scalar keeps its # … text). Consumed by agent-core boot discovery, the brain-core sync indexer, and the query-time source-package grouper — layout recognition must never diverge | | .../testing | Mock sessions, mock dispatchers, connector compliance helpers, tempDir, and assertApiMirror — the compile-time drift guard for a cross-package structural mirror | | .../web-common | The platform's single SSRF-guarded outbound HTTP stack. Transport: safeFetchText / safeFetchJson / safeFetchBytes over ONE core — a DNS-pinned per-request undici dispatcher (requests go through undici's OWN fetch), manual redirects re-validated per hop, a streaming byte cap, and a whole-operation timeoutMs (it bounds the entire call including every redirect hop, not each hop separately). HTTP ≥ 400 bypasses the content-type gate so rate limits surface as status, not type errors; the gate itself is prefix/suffix tolerant (text/*, application/*+json) and treats a MISSING content-type as allowed — it is ingestion policy, not an SSRF floor. safeFetchBytes returns the raw Uint8Array and is the only path that survives binary content (PDF, images); safeFetchText decodes on top of it. The byte cap is a scalar with an optional per-type override, maxBytesByContentType — DATA, not a callback (a callback can throw mid-fetch or answer NaN, and bytesReceived > NaN is permanently false, i.e. no cap at all), matched with the same wildcard matcher as allowedContentTypes, resolved on the FINAL hop from the served type and used by BOTH the Content-Length pre-check and the streaming cap; a non-finite/non-positive value and an ABSENT content-type both fall back to the scalar, so a ceiling is never raised for a type the response did not claim. The WASM guest path deliberately does NOT get it: createGuardedFetch holds ONE scalar cap, stated once as MAX_GUEST_HTTP_RESPONSE_BYTES and fed to both the fetch and Extism's own readBodyUpTo. Defaults resolve explicit option ?? kernelTunable('webFetch…' / 'webToolsUserAgent') ?? hardcoded via the contracts kernelTunables seam. Abandoned responses are cancelled and the dispatcher is destroy()-ed (not close()-d — close() waits for the very request the caller just gave up on). Policy: networkPolicy validates private-IP / port / redirect targets. IPv6 is judged on the parsed ADDRESS, never on its spelling — an embedded IPv4 (v4-mapped ::ffff:… in either the dotted or the compressed-hex form the URL parser produces, the deprecated v4-compatible form, and the NAT64 64:ff9b::/96 prefix) takes the IPv4 verdict, and anything unparseable is blocked. A mixed DNS answer FILTERS the private addresses and pins the first public one (selection is the enforcement: the dispatcher pins that IP and every hop re-validates); zero survivors still fails closed. Ports default to [80, 443, 8080, 8443]. The outbound host-allowlist reads the host-injected setOutboundAllowedHostsSource hook — globalThis+Symbol.for anchored — so the webToolsAllowedHosts admin key applies LIVE, env WEB_TOOLS_ALLOWED_HOSTS as provider-less fallback; that knob only ever NARROWS. The ONE widening knob is WEB_TOOLS_ALLOW_SHARED_ADDRESS_SPACE, a boot-time env constant (deliberately not platform config — see below), default OFF, which opens the CGNAT 100.64.0.0/10 and benchmark 198.18.0.0/15 ranges for deployments behind fake-IP VPN resolvers. Vocabulary: ranking (90-day freshness decay with a per-call freshnessHalfLifeDays override, domain-authority prior, GEO/AI-slop heuristics), records (trimText for snippets, clampText for document bodies), web tool contracts + the structured WebToolError vocabulary — where a blocked target reports host_not_allowed / port_not_allowed specifically but a private address stays a generic blocked_by_policy, so the tool cannot be used as an internal-hostname oracle. Classification reads the error's cause chain as well as its message (undici reports a reset, a connect timeout and a certificate failure alike as TypeError: fetch failed), and feeds the branch tests only — never a model-visible string. TLS_ERROR names a certificate failure without naming the certificate or the host, and is reachable only for a target that already passed the address floor. Guest egress: createGuardedFetch({policy, maxBytes, timeoutMs, maxRedirects, networkRetries?, userAgent?, onResult?}) returns a typeof fetch façade over the same core for callers that must be denied SSRF reach but cannot survive an exception — the WASM sandbox wires it as Extism's fetch option. It takes the COMPLETE OutboundPolicy as a parameter (so an operator-designated destination stays expressible and the private-address rule is never restated), validates the verb against an allowlist, coerces and VALIDATES caller headers (field-name token charset, no CR/LF, framing headers dropped — undici throws on the rest), and maps every failure onto a status: policy/DNS refusal 403 with one generic body, 405, 413, 502, 504. Operator-endpoint egress: resolveOperatorEndpointEgress(entry, {apiKey?}) (see Operator endpoints below) is the streaming counterpart for a platform.config-declared endpoint — same resolver and private-address floor, but its own policy and no body buffering. Image bytes: imageSniff holds the ONE raster magic table (RASTER_MAGIC_PREFIXES, MIN_MAGIC_BYTES) behind two deliberately different questions — the permissive matchesMagic(mime, bytes) (a known raster type its bytes contradict; an unknown type PASSES — the stored-mime normalization depends on that) and the strict sniffRasterImage(bytes) (PNG/JPEG/WebP from the bytes alone, null for everything else, SVG and GIF included) with APPEARANCE_IMAGE_MAX_BYTES (256 KiB, a code floor) for appearance pictures. Server-side only — it lives in the kernel so the web tools, the WASM guest and the future web:// source connector share one fetch path. Not re-exported from the root barrel. | | .../icons | The platform icon library — ZERO imports, read by the validators, the authoring preflight and every picker: ICON_LIBRARY (~270 curated lucide names, each with a category and search keywords), ICON_CATEGORIES, ICON_ALIASES, normalizeIconName (PascalCase, lucide-kebab or an alias → the library name, else null), isLibraryIcon, unknownIconFinding (the ONE warning sentence — an unknown icon is a load WARNING, an ERROR only in check-package.sh). See Icons and appearance below | | .../client | createPackageApi + the React PackageApiProvider / usePackageApi / usePackageContext and the imperative accessors (getPackageApiImperative, tryGetPackageApiImperative, tryGetPackageApiImperativeAsync), the client-side host registry (createPackageHostRegistry / PackageHostRegistryImpl), the icon library and its components (ICON_LIBRARY/ICON_CATEGORIES/ICON_ALIASES/normalizeIconName/isLibraryIcon, ICON_MAP / Github / resolveLucideIcon / resolveIconWithFallback), the ONE appearance picker IconPicker (+ AppearanceValue, browser-side downscaleImageFile, and the zero-import APPEARANCE_IMAGE_MAX_BYTES), the agent appearance authority (AGENT_COLOR_PRESETS, resolveAgentIcon, getDefaultAgentColor, resolveAgentColor), the generic responsive hook (useContainerWidth, a default 900 px wide threshold with 60 px hysteresis and one observer per root), the Skill launcher family (SkillLauncher, SkillButton, useSkillHandoff, consumeWidgetNav, WorkspaceHostPortProvider, useWorkspaceHostPort — see below), and a pure re-export of the client-safe access primitives (hasFeature, rolePriority, isBuiltinRole, canAssignRole, isValidRolePriority, BUILTIN_ROLE_PRIORITY, MIN_/MAX_/WEAKEST_/DEFAULT_CUSTOM_ROLE_PRIORITY) so package UI applies the SAME rules the server enforces instead of hand-rolling a second copy. Package UI must import them from HERE, not from .../access (that subpath also pulls PackageAccessCatalog/PackageFamilyResolver, which are not vetted for a client bundle). TOOL_NAME_PATTERN / isWireSafeToolName remain UI re-exports of the zero-import validation/toolName.ts leaf. Mixed server/browser consumers, including the per-run tool catalog, use .../tool-name directly: this UI barrel reaches React hooks and is invalid in a React Server Component graph. The .../validation barrel reaches node:fs and is invalid in a browser graph. Both failures escape type gates. Keep every module re-exported in this block zero-import. UI is never the floor — the server re-checks every one. The barrel also carries the chat-card contract (registerCardComponent & co. with setFallbackCard, CardProps/ToolCallData/CardStatus, cardHeights, composerContracts, cardTypeClaimedByTool, the pure tool-input readers, row identity/disclosure state, and CardShell + the live-motion primitives LiveSpinner/LivePulseDot/ToolStatusIcon) — re-exported by agent-core's chat cards so every package shares the kernel chrome and one React context — and externalFrameProfile (+ EXTERNAL_FRAME_SANDBOX / OPAQUE_FRAME_SANDBOX), the ONE predicate that gives a first-party frame on a foreign https: origin its own-origin sandbox (never allow-top-navigation*) and every other frame the opaque one. Because this subpath is a shared-module table entry, every package UI module reads the host's ONE instance of it | | .../bridge | Cross-widget state bridge (set/get/subscribeCrossWidgetState) | | .../client/shared-modules | The ONE shared-module table (SHARED_MODULES = tier 1 only: React ×5 + @neuralis/package-system/client, provided by the host; tier 2 is PACKAGE-DECLARED — app.module.provides, subpaths of the declaring package's own name, declareSharedModules), read by BOTH neuralis-build ui (each id becomes a globalThis shim, never a bundled copy) and the host's module loader (publishSharedModule, planUiModuleInstall — providers before consumers, a missing provider skips the consumer). Also the ONE host-API version (UI_MODULE_HOST_API_VERSION) and the build record's format and parser (UiModuleBuildRecord, parseUiModuleBuildRecord; reactMajor is absent only for a bundle that reads no React id, and the host refuses a React-reading record without one as react-major — a module built before the field existed is rebuilt with build:ui after a host/kernel upgrade, by its owner when the package ships it prebuilt), and the ONE verdict both the image build and the host apply to a record, checkUiModuleBuildRecord (+ resolveBundleReactMajor), under ONE vocabulary UiModuleRecordRefusal (not-built · shared-imports-unreadable · host-api-version · react-major · no-install-export) — the last read off the bundle against UI_MODULE_INSTALL_EXPORT, the install<Name>HostComponents name the host loader attaches through. Zero imports; a second, different instance of a shared id THROWS | | .../theme.css | The workspace @theme design tokens (dark defaults). The union sheet's prelude imports it, so a token change reaches every runtime-attached package UI with no package rebuild; the host's build sheet still carries its own @theme copy, and a test pins the two equal | | .../runtime/wasm, .../runtime/mcp, .../runtime/wasm-bridge, .../runtime/wasm-build, .../runtime/ui-build, .../runtime/ui-sheet | Lazy-loaded sandbox runtime and UI build tooling — split out so Next.js bundles do not pull esbuild + @extism/extism native binaries by accident. ui-sheet (compileUnionSheet) takes its Tailwind engine by INJECTION, so the kernel itself carries no native CSS dependency | | .../pdk-guest | The guest PDK a WASM _packages/ build links — node-free (platform: 'neutral') wrappers over the 8 host functions (ctx.data.*, ctx.callRoute cross-package route dispatch with the caller's host-threaded real session, the ctx.fs.* sugar over brain-core's file routes, ctx.context/ctx.log, and the currently-inert ctx.config ({}) / ctx.emitEvent (no-op)), the makeToolCtx/makeEnsureInit factories, and the dispatchTool/dispatchRoute the generated handleTool/handleRoute delegate to. The build aliases this specifier to the installed package-system PDK (the data zone has no node_modules). Those eight are the complete set of NEURALIS capabilities a guest can reach — say it that way, not as "the guest's entire import table". A built guest also links the toolchain's WASI preview-1 imports (measured: 12, via Node's node:wasi), so the stronger claim is false; it is not a hole, because that surface is instantiated with no args, no environ and no preopened descriptors, but an invariant stated too strongly is worse than a bounded one. |

Why the shared-address-space switch is an env constant and not a config key. It is the only knob here that WIDENS the SSRF floor, and it is read once at module load. Platform config would be the wrong home twice over: platform.config PATCHes the platform-global platform.json, so any role an owner granted that feature could loosen the whole deployment from inside the app; and the PlatformConfigStore cascade resolves file-override → envFallback → default, so a platform.json PATCH would beat the operator's env value even if it were only declared as a fallback. The blast radius is deployment-wide, not web-tools-wide: isBlockedIp sits under resolveAndValidateTarget, which every safeFetch* caller passes through — including agent-core's git OAuth token exchange.

The wasm/mcp entry points are runtime sandboxes; their types live in contracts. Import the runtime entry points only from server-side bootstrap code that explicitly needs the sandbox implementation. The host-side WASM route gate (createWasmRouteDispatcher/evaluateWasmRouteWiring/ parseRouteManifest) is pure and re-exported from the root barrel.

Icons and appearance

ONE icon vocabulary serves every surface: icons/iconLibrary.ts (the .../icons subpath, zero imports) names ~270 curated lucide icons, and client/iconResolver.ts's ICON_MAP holds exactly those components — a satisfies Record<IconLibraryName, …> fails the build on a missing or extra key, and test/icons/iconLibrary.test.ts checks each name against the installed lucide-react. A manifest may write the PascalCase name, its lucide-kebab form or an ICON_ALIASES entry; resolveLucideIcon normalizes and stays MAP-ONLY (no lucide passthrough — a name outside the library is null). validateContribution (widget/dock/card), validateManifest (team[].appearance) and validateWorkflowTemplate warn on an unknown name; the package still loads with the fallback glyph. Adding a name is one row in each of the two files.

Agents use the same library: resolveAgentIcon resolves any library name and falls back to Bot only outside it (there is no narrower agent list). On the colour side AGENT_COLOR_PRESETS is the shared deterministic 12-colour palette, getDefaultAgentColor(index) indexes into it, and resolveAgentColor(configured, index) gives a configured colour precedence. IconPicker is the ONE picker (search over names, category labels and keywords; a category strip; roving keyboard focus; palette + custom colour; an optional picture slot that downscales to 256 px and re-encodes in the browser — convenience only, the server sniffs and bounds the bytes). It never fetches or persists: the caller owns storage. This module stores no user or agent data; agent appearance stays in AgentRecord.config.appearance (agent-core) and the human profile in the host UserStore.

Stability tiers

Annotated on every public type. When you see one of these markers in a JSDoc comment, treat it as a contract guarantee for refactors.

  • Frozen — PackageDefinition.id/name/version, PackageTool.name/inputSchema, PackageFile.{id,path,category,uri,connector,osUri,containerDir} quadruple, RuntimeStackInfo shape, UriPolicyContext, CrossPackageSession, SystemSession sentinel constants, boundToolResult cap defaults, CONNECTOR_PORT_VERSION = 4, ConnectorPort method signatures, PackageRuntime.skillScripts ∈ {'host','wasm-only','none'} (closed enum).
  • Stable but evolving — PackageUriPolicies allowed-prefix list (currently packages://<self>/, data://<self>/), ToolRiskCategory enum, UiElement.kind variants.
  • Host → package ports (contracts/hostPorts.ts) — every PackageInitContext carries config (live platform-config read; an unregistered key throws) and platform (lazy bootstrap facts: runtimeStack, packageUriPolicies, packageSourceDeclarations, machineWrittenPaths, connectorRegistries, hostIdentity — each getter throws before the host published them). Both throw PackageContextDeniedError for a package below host-assigned first-party. hostPorts (members, audit, session resolver, scope-hidden resolver, package maintenance, package diagnostics, agent lifecycle, credential writers, the Codex OAuth credential writer, the trusted app origin, MCP token store, git OAuth, credential store, governance, platform config admin, credential-use admin) is present ONLY on a host-assigned first-party context (hostPortsFor), and a source scan keeps the credential writers inside agent-core, the credential store / credential-use admin / governance / platform-config ports inside admin, and bans a globalThis fallback beside any port read. The Codex writer is restricted by the host to the one id CODEX_CREDENTIAL_ID and re-derives its four scope tiers on every call; no member takes a credential id or a writer class. Optional packageDiagnostics reads live runtime declarations and aggregate health; the host throws before successful runtime readiness. Consumers keep their feature gates and bounded response projections; declarations grant no authority. BootstrapCompleteContext is the init context itself.
  • Service contracts (contracts/services.ts, contracts/runtimeProvider.ts) — a first-party manifest's provides names what it implements (runtime · session-ticket-verifier · route-token-verifier · agent-directory · channel-gateway · package-source-roots · mcp-server · oauth-callback:<prefix>); runtime/serviceRegistry.ts resolves exactly one runtime and one session-ticket-verifier over the builtin set (0 or 2 is a boot failure, overlapping OAuth prefixes too), and at most one of every other singular contract. route-token-verifier (verifyRouteToken(token, expectedResource)) is what the host catch-all asks for an OAuth bearer on its package-route API — a live SessionContext or a refusal code; when no provider is present that arm denies; loads the runtime provider (dist/src/runtimeProvider.js, export runtimeProvider) and serves the others by contract id from the provider's PackageRuntimeApi.services. The RuntimeInstance is the host's only handle: loader port (load/reload retain the raw manifest id and owning project, unload/start, and load status carries its definition), route dispatch, whenReady(), the discovered realtime channels, health(), notifyCredentialChanged, drainAll(). Channel services expose safe descriptors and authenticated OAuth start; callback services re-resolve the captured project and optionally return channel tokens. The optional singular package-source-roots service preserves each source's URI/container root, project/user/agent scope, enabled flag, recognition flag and kind for internal runtime activation, and subscribePackageSourceRoots(listener) tells the host which project to re-list after a committed source change (after the write, never inside it); member listings retain their existing scope/policy gates.

PackageDefinition — the canonical shape

Declared in package.json under the neuralis key. Flat identity fields

  • standard capability arrays + optional Neuralis-specific blocks. Field groups (see src/contracts/package.ts for the full type):
  • Identity. id, name, version, description, descriptionForModel, authors, tags, license, icon, homepage, repository.
  • Capabilities (cross-platform). The complete array set is tools, commands, resources, hooks, the five first-class file arrays (skills/instructions/rules/agents/docs), workflows, connectors, sources, credentials, configSettings, policies, uriPolicies, team, provides, events — a prose list shorter than this one is stale. commands is the legacy definition-only form (no model invocation — published to external MCP clients as prompts); workflows are declarative workflow templates (see below); connectors register source KINDS while sources declare concrete source INSTANCES of them (see below); credentials and configSettings are the two DECLARATION arrays — they surface a credential id in the admin catalog and a platform-config key in the admin Config tab respectively, and declaration ≠ grant for both (see below); policies are tool-call gates and uriPolicies the URI access baselines; provides names the host service contracts a first-party package implements (see Stability tiers above); events declares the event types a first-party package emits (see Events under Dispatchers).
  • Neuralis UI. app.surfaces[] — widgets, dock entries, cards. The legacy flat ui[] array is no longer read. app.module ({ entry, css?, provides? }, paths package-relative under dist/) is a first-party package's ONE prebuilt browser module; provides lists the tier-2 shared modules it publishes (subpaths of its own id). PackageRuntimeSnapshot.ui (PackageUiAttachments) types what the host's runtime route attaches beside the snapshot — the kernel runtime never sets it. provides and app.module count only on a POST-merge, host-assigned first-party manifest: validateManifest warns (never errors) on any other tier and restrictFirstPartyManifestFields(definition, trust) drops them at load.
  • Requirements. requires.providesFeatures, requires.defaultRoleGrants, requires.packages, requires.accessFeature. providesFeatures entries are a bare id string OR the {id,title,description} ProvidedFeature object form (C9) — normalizeProvidedFeatures / providedFeatureIds read both; collectFeatureMetadata merges the metadata. requires.accessFeature (optional) is a SINGLE feature id that gates the WHOLE package's visibility (base-access): a caller lacking it sees none of the package (validated shape-only — like a route feature, it may name a feature provided elsewhere). Undeclared ⇒ visible (preserves the import-unchanged floor). A declared accessFeature is auto-registered into collectAllFeatures / collectFeatureMetadata so it is GRANTABLE in the admin Roles UI (an owner can grant base access to a role) — featureAudit is unaffected (it reads provided from manifests, not via collectAllFeatures). The host can ATTACH an additional requirement per-package via a restrict-only override (ProjectRecord.packageAccessFeature); the effective gate (manifest ∪ override, each independently required) is enforced through the shared hasFeature. A feature's CONSUMERS (which tools/skills/commands/widgets/docks it gates) are NEVER stored on the manifest — buildFeatureConsumersIndex(definitions) derives them at boot/request time (route consumers excluded by design; see its scope note). auditFeatures (@neuralis/package-system/testing) is the C7c lint core: unknown-id + declared-but-unreferenced analysis over collected references.
  • Operational metadata. source (where the package came from), runtime (how it executes), access (trust + external policy), meta (hosts, families, distribution), dataLayout (the package's declared data:// layout — directories package-level, agentDirectories per-agent — validated fail-closed by validateManifest (segment guard + a rendering charset, no duplicates, unknown keys rejected) because agent-core renders it VERBATIM into every agent's cached <runtime_stack> prefix as a data_areas row; the zone root is sanitizePackageId (./paths), never the id). dataLayout.machineWritten[] is the one layout field that is NOT rendered: { path, class } rows naming the files the package writes on its own (metrics · log · runtime-text), each a glob relative to the zone (validateManifest refuses a leading /, empty or ../. segments and any character outside [A-Za-z0-9._*-]). collectMachineWrittenPaths anchors them to the zone (trusted-or-above packages only) and rides ctx.platform.machineWrittenPaths(); seedExcludedMachineWrittenGlobs keeps the classes in SEED_EXCLUDED_MACHINE_WRITTEN_CLASSES (metrics), which a NEW project's data source seeds into sync.exclude once, at creation — a preference seed, never a floor, never re-applied to an existing config.
  • Lifecycle. lifecycle — host calls init/start/onBootstrapComplete/ drain/stop/provisionProject/deprovisionProject/onPrincipalRevoked/ onCredentialChanged/health/handleToolCall/handleRoute. onCredentialChanged is value-free ({ credentialId, scope, change }), fanned out bounded per package; health answers the closed PackageHealthReport vocabulary (ok · starting · degraded · failed + closed reasons, never free text), validated by normalizePackageHealthReport; drain runs once at shutdown before stop. Runtime-only, never serialized. onPrincipalRevoked(PrincipalRevokedEvent) is the ONE revocation signal (disabled / deleted / sessions_reset user-wide, membership_removed / project_archived with a projectId): a package closes the live state it holds for that user through its existing stops — it never decides access, since a revoked principal gets no session from any producer. The loader fans it out with a per-hook timeout. deprovisionProject({ projectId }) is provisionProject's pair for a PERMANENT project delete: the package removes every resource it keys by the project outside the project's data tree (containers, volumes, vector points), idempotently, and THROWS when anything is left. The loader walks the hooks in sequence, each bounded by a timeout, and the first failure stops the host's purge before any file is removed with a ProjectDeprovisionError naming the package. Only an in-process first-party lifecycle can implement it.

The id is checked at validation time against a reserved-route set (widgets, dock, install, query, command, commands, publish, rescan, runtime, stream, plus templates as a deliberate forward-reservation for the planned publish surface) so a package id never shadows a literal segment in the Next.js host route tree — a derive-and-verify test (reservedPackageIds-style, in validation.test.ts) reads the host route dir and asserts every live segment is reserved. The id must also be a well-formed lowercase npm name (name or @scope/name); .. segments, extra path separators, and the <self> token are rejected.

PackageFile — first-class markdown contributions

Markdown contributions (skills, instructions, rules, agents, docs) are first-class capability arrays on PackageDefinition — skills, instructions, rules, agents, docs — exactly like tools. Each element is a PackageFile carrying a required category ('skill' | 'instruction' | 'rule' | 'agent' | 'docs') set authoritatively by the loader from the contribution's flat source folder. category is deliberately NOT called role — role denotes the user rank (owner/admin/member) everywhere else in the system.

The runtime snapshot mirrors this with five typed arrays (snapshot.skills/instructions/rules/agents/docs). Category-specific consumers read the array they need directly (snapshot.agents); only genuinely cross-category features (the per-agent active-file catalog, the <packages> overview) fan over all five via the allFiles(snapshot) helper.

Model-facing descriptions are contracts

The package overview always exposes each visible package's model description and each visible skill's frontmatter description; the skill body arrives only after activation. Author a skill description as a routing contract: the outcome it enables, the natural request or failure state that should trigger it, and the decisive distinction from neighboring capabilities. Keep the procedure in the body.

Visible tools are sent separately with their root description and full input schema on every model step. The root description owns tool selection plus the immediate recovery/safety boundary; property descriptions own only input-specific meaning. Do not duplicate a long skill/rule procedure into the schema, but do not treat descriptions as display copy either—the model operates from them.

files — the skill bundle enumeration (skill scope = the whole directory)

A skill's SCOPE is its full directory (dirname(path)), not just the SKILL.md entry file. PackageFile.files?: string[] is the dir-relative enumeration of the bundle, discovered at scan time: SKILL.md first, then scripts/**, references/**, assets/**, then any other skill-protocol file in code-unit order; capped at 64 entries (the SKILL.md entry has a reserved slot). Code-unit order is compareCodeUnits (.../source), the kernel's exported locale-free string comparator for any order that reaches the model's prompt: localeCompare follows the runtime's ICU data, so an order built on it can move between images. Skill-only today — flat categories (rules/instructions/agents/docs) carry no enumeration. There is no separate dir field: the bundle directory is always dirname(path) ('.' for a root-SKILL.md package whose root IS the skill). files is display/advertisement metadata (UI folder rows, the <packages> overview [files: …] suffix) — never a permission boundary; the S1 shell gate's active-SKILL_DIR allowance covers the directory itself.

requires — per-contribution feature visibility (S2)

PackageFile.requires?: { features?: string[] } declares which features a caller must hold to even see the contribution — parity with the same field on PackageCommand / WidgetUiElement / DockUiElement. The single authoritative predicate is meetsRequires(session, requires) (access/meetsRequires.ts, built on hasFeature): no declaration ⇒ visible, '*' passes everything, otherwise EVERY listed feature must be granted (AND). A failing file is hidden entirely (prompt injection, <packages> overview, skills accessor, files catalog, client snapshot) — never rendered as [OFF]/locked, so non-holders cannot infer it exists. Execution stays independently gated (route feature, shell uri-policy, credentials). Declaring a feature id no package provides makes the file wildcard-holder-only ('*') by construction — an intentional deny-by-default property. Authors set it via the flat requiredFeatures: [a, b] frontmatter key (the discovery parser does not support a nested requires: form). Surfaces must call meetsRequires, not inline their own copy of the wildcard/AND logic.

Tools carry the same requires (S2b). PackageTool.requires?: { features?: string[]; escalations?: string[] } is the ONE declarative source for tool feature-gating — it replaces the legacy manifest policies[] kind:'access' gate (that compile path is removed). A caller lacking a required feature never sees the tool in the model-facing catalog (in-stream + hosted MCP) nor in the client snapshot, and a dispatch is rejected with a generic, non-leaking message. escalations is catalog/lint metadata only — additional features a handler may demand from its INPUT at runtime (e.g. machine_use's drive-class actions need machine.drive beyond the machine.read baseline); it does NOT gate. The shape validator is the shared validateRequiresShape (one helper for PackageFile / PackageCommand / PackageTool).

The (uri, connector, osUri, containerDir) quadruple (W4E)

Every contribution also carries up to four coordinates:

  • uri — the fs-tool address (e.g. packages://agent-core/skills/ops/SKILL.md, data://repo/notes.md, brain://…). Undefined for builtin packages whose files are read directly from the container image.
  • connector — the connector kind that serves the URI ('local' | 'brain' | 'webtop' | 'host' | …) — there is no neuralis connector kind; the neuralis:// mount surfaces as a local-kind source. Even when uri is undefined this still reflects the disk layout ('local').
  • osUri — host-side absolute path produced by connector.resolveOsUri(uri) when supported. UI + vectors consume this to render an editor-pasteable path. Undefined for remote / Qdrant-backed files.
  • containerDir — container-internal absolute path. Always set for builtin and _packages/ files because they are present inside the container by image snapshot or bind-mount. Source-fed files set containerDir only when the connector is local-kind AND the source mount maps into the container.

The skill-execution substrate (execute action="skill") substitutes ${SKILL_DIR} from containerDir and ${SKILL_URI} from uri. Builtin defaults: uri/osUri undefined, containerDir filled. User _packages/ defaults: all four filled.

PackageWorkflow — declarative workflow templates (contracts/workflow.ts)

WorkflowExecutionPolicy includes optional modelId, reasoningEffort, contextWindowOverride, and fastMode alongside runtime/concurrency limits. Authored templates and persisted workflow entries are null-free: omission means inherit from the target agent at fire time. Runtime update schemas may accept null as a patch instruction to clear an override, but that sentinel is never stored or admitted by template validation. false is a real fast-mode override, not omission. Capability compatibility belongs to the consuming runtime because the kernel owns the shape, not any provider catalog.

workflows/*.json files are a first-class parallel typed array on PackageDefinition (workflows?: PackageWorkflow[]) — the exact tools precedent (JSON folder → AJV → typed array), deliberately NOT a sixth PackageFile category: templates are structured JSON that must never be prompt-injected, so they are absent from FileArrays/allFiles() and the runtime snapshot exposes them separately (snapshot.workflows: WorkflowRuntimeDefinition[], which is where packageId lives — PackageTool parity).

contracts/workflow.ts is THE one owner of the workflow trigger/schedule union (WorkflowTrigger/WorkflowScheduleConfig) and WorkflowExecutionPolicy: the agent-core engine re-exports these for its persistent WorkflowEntry, and the AJV fragments in validation/workflowSchemas.ts derive from the same module — no second union copy anywhere. WorkflowTriggerTemplate is the authorable form (id optional, derived type), and engine-runtime trigger fields (nextFireAt, fireCount, completed, …) are rejected by the template validator.

Template fields: identity/visuals (id from the filename, title, description in the agentskills.io "what + when" style, icon, color, group — NOT category, which is reserved contribution vocabulary), defaultInstructionTemplate (≤10k), inputs (gh-aw workflow_dispatch.inputs field names: description/required/type string|choice|boolean/default/options — install-form generation + install-time {{KEY}} substitution; the engine fire-time placeholders WORKFLOW_DIR/WORKFLOW_STATE_URI/FIRE_TIME are rejected as input keys), defaultTriggers/defaultStateMode/defaultExecutionPolicy, defaultSessionSetup (the SessionSetup goal + loop working-state seeded into every run's <session> — a formalized goal.objective + checkable doneConditions, a loop bound until: goal|manual + an enforced maxSteps, and an evolve knob; loop.schedule is display-only and never authored — the runner mirrors the trigger. SessionSetup is shared with conversation settings.session, and the length caps MAX_SESSION_* are a hardening floor), channelKinds/requiredCredentials, the delivery block (OpenClaw field names — every mode authorable since the channel gateway landed; same for message/webhook defaultTriggers, with a message trigger's unknown bindingId fail-louding at install), and requires.features (S2 — the ONE meetsRequires predicate, applied by EVERY listing surface itself), and the optional revision? (positive integer, missing ⇒ 1). validateWorkflowTemplate rejects a zero/negative/fractional/string revision. Because an install COPIES the instruction template into the persisted WorkflowEntry rather than live-linking it, revision is how a package signals that already-installed copies are stale: the agent-core engine records templateRef.revision at install and pauses an out-of-date active/draft copy (template_upgrade_required) instead of silently rewriting the creator's instruction. Clearing that pause is an explicit, authorized act of the consumer's own — agent-core upgrades the instance IN PLACE, keeping its id and therefore its run history and state folder; the kernel's part ends at the revision number. The contract also owns WorkflowDeliveryPolicy (per-entry announce/none, streaming reserved) and the member-grade self-scope credential-write vocabulary in contracts/credentials.ts. The RESERVED id classes are three DISJOINT ones (each with its OWN structural predicate, none interchangeable) reached through TWO ports — and the file carries two further host-injected ports that must never be folded into them (MemberCredentialWriter, GitOAuthAppConfigPort, both below), for four in total:

  • the SelfScopeCredentialWriter — the UNIFIED port (Phase B, 2026-07-13: the 2→1 collapse of the two line-for-line-identical narrow ports, the old ScopedCredentialWriter for channels + GitCredentialWriterPort for git). One interface, writeUserScoped(scopeClass, userId, id, value) / deleteUserScoped(scopeClass, userId, id), where SelfScopeClass = 'channel' | 'git' | 'workflow'. It carries the member-grade classes: the channel whitelist (CHANNEL_SELF_SCOPE_CREDENTIAL_IDS + isChannelSelfScopeCredentialId); the git class (git.<host>.pat|.oauth.accessToken|.oauth.refreshToken, built by gitPatCredentialId/gitOAuthAccessTokenCredentialId/gitOAuthRefreshTokenCredentialId, matched by the structural isGitSelfScopeCredentialId over GIT_HOST_CHARSET ^[a-z0-9.-]{1,255}$, disjoint from mcp/channel/data.github); and the workflow class (workflow.<workflowId>.signingSecret, built by workflowSigningSecretCredentialId, matched by the structural isWorkflowSigningSecretCredentialId whose workflow-id charset is DOT-FREE so the family can never alias another). scopeClass selects the PREDICATE + AUDIT TAG ONLY — the write is ALWAYS users/<userId> (no scope-kind arg ⇒ SHAPE ≠ AUTHORITY). The per-class predicates stay disjoint and are re-enforced by the host impl, which owns the class registry as CODE — the contract carries only the type + predicates, no manifest field feeds a self-scope class.
  • the McpTokenStorePort — kept SEPARATE (M2), NOT folded into the unified writer: the outbound-MCP family carries an McpCredentialScopeRef (user|project|global) scope arg + readScoped, so folding it would reintroduce a scope-kind arg and break SHAPE ≠ AUTHORITY. The family is THREE structurally distinct classes, each with its own predicate and a deliberately different DESTINATION — never merge them: mcp.<serverId>.<accessToken|refreshToken|clientId|clientSecret|expiresAt> (isMcpSelfScopeCredentialId, OAuth tokens and the persisted access-token expiry), mcp.<serverId>.env.<ENVKEY> (isMcpSidecarEnvCredentialId, a local sidecar container's env), and mcp.<serverId>.header.<KEY> (isMcpHeaderCredentialId, an outbound HTTP header transmitted to a caller-configured external URL). All three parse STRUCTURALLY — serverId up to the FIRST dot, then a literal env./header. segment — so a serverId literally named env or header still parses; never split on .env./.header.. isReservedSelfScopeCredentialId lists all five predicates EXPLICITLY rather than relying on fallthrough.

RED LINE (JSDoc-pinned on each port): users/<id> writes only for the self-scope writer, predicate re-enforced at the port, id-only audit, ONE call-site per class — never widen the prefix/suffix set, never merge two classes into a super-predicate, never add a second call site. The git class also owns the pure HTTPS-Basic username mapping — GitHostType × GitCredentialType → resolveUsernameConvention → GitUsernameConvention → resolveUsername (NEVER empty; token is always the password).

The remaining two ports are deliberately outside the class-dispatched writer. MemberCredentialWriter (writeUserScoped / deleteUserScoped / listUserScoped) is the member-BYOK port: the ONE broad writer, and a DENY-list — it accepts any id that passes isValidCredentialId and is NOT isReservedSelfScopeCredentialId, i.e. exactly the complement of what the self-scope writer allows. Mixing the two polarities into one port would need a mode: 'allow' | 'deny' switch, which is floor-bug fuel, so it stays separate; the users/<userId>-only SHAPE ≠ AUTHORITY rule binds it identically (no scope-kind arg, ever). GitOAuthAppConfigPort (readClientId / readClientSecret) is READ-only and hard-wired to the two fixed GitHub OAuth App ids — resolved through the standard agent→project→user→global cascade, NOT global-exact. It is deliberately not a generic caller-supplied resolver, so it can never become an arbitrary-id read primitive; the git-remote OAuth flow reads clientId at the session-derived scope and clientSecret at the flow's CAPTURED scope.

Alongside the member-grade WRITE vocabulary, contracts/credentials.ts owns the package DECLARATION vocabulary: PackageCredential (an entry of the optional PackageDefinition.credentials?: PackageCredential[] array — exactly { id, label, category?, oauth? }, where oauth is a DISPLAY hint), the CredentialSource union 'package' | 'skill' | 'manual', and the CredentialCatalogEntry shape the admin UI + manage-credentials skill read (never carries the secret value, only hasValue + declaredBy + declaredByTrust + the oauth display hint + an optional status?: CredentialStatus — { kind: 'valid' | 'expiring' | 'expired' | 'refresh_failed' | 'unknown'; expiresAt?; lastRefreshAt?; note? }, the lifecycle of an OAuth-class value that the package OWNING the token computes; the kernel carries only the type, and note never holds a token fragment, an identity-provider body or a store path). description / isSecret / isRequired / scopeHint / envVar were RETIRED (CRED-TAIL INC 7): declared but read by nothing, so they taught a contract the system did not enforce — a manifest still carrying one loads with a warning. The kernel exports isValidCredentialId — the SINGLE charset gate (^[A-Za-z_][A-Za-z0-9_.-]*$, ≤ 128, no slash/leading-dot, since the id becomes an <id>.enc.json path segment) reused by BOTH the manifest credentials[] validator and the member BYOK writer. validateManifest runs a light hand-written block over credentials[] (non-empty id/label, id passes isValidCredentialId, no duplicate ids, category/oauth type checks) plus ONE security gate: a RESERVED self-scope id — the git (git.<host>.pat / .oauth.*), channel, and MCP (mcp.<server>.*) classes, i.e. isReservedSelfScopeCredentialId) — may only be declared by a first-party package. Those classes have host-owned resolution semantics; a _packages/ drop squatting git.github.com.pat under its own label would be indistinguishable in the admin catalog, and once an owner filled it in at global scope the token would cascade-resolve for every member without their own. The gate is deliberately NOT the config-style blanket trust filter: a package's OWN namespace is declarable at any trust tier (the import-unchanged floor), because otherwise an owner could never set it and the package could not work. Declaration ≠ grant (M1 floor): a credentials[] row is catalog/display metadata ONLY — it surfaces the id for an OWNER to set and carries NO self-scope write authority; consumption still resolves against the caller's real scope via CredentialResolver.

Trust also governs catalog attribution: a first-party declaration wins the label/category/oauth seed regardless of load order, a later non-first-party declarer of the same id only joins declaredBy, and declaredByTrust carries the tier per declarer so a UI never renders an untrusted declarer as the platform. A declared id that nothing resolves is a defect, not a placeholder — the agent-core credentialDeclarationDrift guard asserts declared ⟷ consumed in both directions from derived sources.

The READ port is CredentialResolver, and it has exactly ONE working method: resolveCredential(id, scope), cascade-resolved agent → project → user → global (most-specific wins). resolveForKind(kind, scope) is a dead stub — the host returns {}; it survives only as the Phase-C anchor for a future kind→ids mapping, so a package that builds on it silently gets nothing. There is deliberately no resolveEnv(key): it used to hand every loaded package — untrusted _packages/ drops included — an unbounded, un-blocklisted process.env[key] read (making NEXTAUTH_SECRET and every provider key readable) through the very port whose purpose is to REPLACE direct env access. It had zero production callers and was removed; boot-critical infra reaches packages as typed config (configSettings[] + envFallback), never raw env.

contracts/config.ts owns the parallel platform-CONFIG declaration vocabulary (tunable-tanager): PackageConfigSetting (an entry of the optional PackageDefinition.configSettings?: PackageConfigSetting[] array — flat camelCase key / label / description / type: number|string|boolean|json / default / optional envFallback / editable / category / number-only min/max) plus the kernel single-source isValidConfigKey (^[a-z][a-zA-Z0-9]{0,63}$) and the looksSecretShaped WARN heuristic. The host PlatformConfigStore is a pure registry+cascade engine (file override → envFallback → default) with ZERO hardcoded schema: first-party packages' declarations register at bootstrap, BEFORE the first package init (the kernel collectDeclaredConfigSettings collector, ./access), the host's own residual keys via the same mechanism (declarer 'host'). validateManifest runs a light block over configSettings[] (charset, type enum, default typeof-match, min<=default<=max, per-manifest dup reject, secret-shaped WARNs on key AND envFallback name, json-envFallback WARN). Declaration ≠ grant here too — a row only surfaces the key on the feature-gated admin Config tab — and, STRICTER than credentials (display-only), config declarations are honored for first-party packages ONLY: envFallback projects an env var's VALUE into admin-visible state, so an untrusted _packages/ declaration would be an env-exfiltration primitive.

Schedule guardrails: maxRuns (cron) and endAt (cron + once) complete a trigger when hit — schedule-hygiene bounds, NOT a cost-quota system (every fire already runs under the creator's per-user USD spend limits + rate limits). Installing instantiates a mutable WorkflowEntry in data:// with templateRef recorded, creator = the installing caller, status draft. workflows is in RESERVED_DIRS — nothing under it is ever a markdown contribution, and source-synced workflows never load.

Source-contributed packages (the third recognition tier — source/packageLayout)

Beside packages:// runtime drops and deps-sourced builtin-class packages, a synced brain-core filesystem source (repo://, data://, …) can contribute source packages: groups of indexed markdown contributions recognized from the vector index at query time. The contract lives in src/source/packageLayout.ts (pure, I/O-free) and has three parts:

  • The trigger-category table (categorizeContributionPath): which source-relative paths get a contribution category at sync time — SKILL.md anywhere → skill (plus the legacy commands/*.md single-file form), instructions/** → instruction, the universal identity basenames (AGENTS.md, CLAUDE.md, CLAUDE.local.md, GEMINI.md, .github/copilot-instructions.md) → opt-in instruction, rules/** + .cursor/rules/*.mdc + .cursorrules/.windsurfrules → rule, agents/** + *.agent.md → agent, docs/** → docs (README/templates excluded), manifests → package (anchor iff content is usable), .mcp.json/hooks.json → connector/hook (indexed only). Patterns match inside the marker dot-dirs (ALLOWED_DOT_DIRS: .claude .agents .cursor .codex .gemini .windsurf .github .opencode .kilo .roo).
  • The admission rule (admitsContribution(hit, frontmatter)): the table says WHICH category a path would get; admission says whether the file IS a contribution. A markdown is admitted where the ecosystem protocol makes the filename its identity (identityFromPath on the hit — SKILL.md, commands/*.md, the identity basenames, .cursorrules/.windsurfrules, .cursor/rules/*.mdc) or where its frontmatter declares one (id or name as a non-empty string — hasFrontmatterIdentity). A folder-bound .md (agents/**, rules/**, instructions/**, docs/**) and *.agent.md without either is a plain indexed file: a .claude/agents/references/*.md reference folder is NOT a dozen agents. This is the first-party loader's rule applied to every package form — the loader (first-party, _packages/ drops, recognizesPackages roots), the brain-core sync indexer (which also re-derives a stale trigger category on the next full sync, payload-only) and the marketplace scanner all call the ONE predicate; an inline copy is a regression.
  • The grouping rules (detectPackageRoots): usable-manifest anchor → marker dot-dir (one package per dot-dir — repo://.claude is ONE package) → category-dirs root → bare-skill dir → the RESTRICTED implicit source-root package (root-level identity files + root-level category dirs only; nested unclaimed nodes stay indexed-but-unmapped). Nearest root wins; grouping is query-time, so layout changes self-heal without index migration.

Two functions answer "is this manifest usable", and they answer differently ON PURPOSE. isUsableManifestContent (source/packageLayout.ts) is a grouping heuristic on the vector plane: it requires only that a neuralis key be an object, deliberately generous so foreign ecosystems (plugin.json, gemini-extension.json, neuralis.package.json) group correctly. It confers no trust, no registration, no execution. validateManifest (validation/) is the kernel gate: fail-closed, run before anything is registered. So a manifest the kernel REJECTS can still anchor a source-package root (rule 'manifest', precedence 0, above every folder rule) and still supply the display name that reaches the model's <packages> overview.

Do not "align" them. Narrowing the heuristic to the kernel's rules would silently stop grouping every non-Neuralis ecosystem tree — a capability cut disguised as hardening. What the asymmetry requires instead is that the CONSUMER bound and fold what it renders: the name is read-gated by uri-policy (a file-denied manifest is refused a content-derived name), and agent-core's <packages> builder folds the container tag out of it and caps its length.

What sources will NEVER load: code surfaces. No tools, hooks, routes, lifecycle, team, workflows, or connector registrations come from a synced source — markdown contribution categories only; package/connector/hook nodes are indexed and categorized but never executed or registered. Dropped capabilities are logged, never best-effort-loaded (neuralis-universal import doctrine).

Tool authoring rules (cross-platform MCP-standard)

Every package tool follows MCP shape. Authoring rules the validator enforces (validateToolSchema + validateXNeuralis):

  • inputSchema.$schema set, inputSchema.title set, inputSchema.type === "object".
  • Root additionalProperties: false.
  • No legacy x-tool / x-output / x-validation extensions — only the canonical x-neuralis block, whose accepted top-level keys are exactly nine: dotted operation, explicit transport, and the optional family, annotations, ui, defaults, requires (the S2b feature gate + the non-gating escalations metadata — see above), askOnKeys and concurrencySafe (below). Any other top-level key is rejected.
  • No session-context input keys — EVERY SessionContext field (the set is derived from keyof SessionContext: userId, projectId, agentId, conversationId, requestId, toolUseId, delegateRunId, delegateMode, role, priority, grantedFeatures, spendLimits, llmRateLimitRpm, agentAccess, agentOwnership) plus the bundled session name are rejected as top-level input properties. The ban is ROOT-ONLY by design — the same names are legitimate domain fields when nested (a todos[].priority enum, a members[].userId), and identity is always read from the verified session, never from input.foo.userId. The reserved SET is derived from keyof SessionContext so it can never drift from the identity type, even though the CHECK is scoped to the root.
  • annotations.category (ToolRiskCategory) is REQUIRED for native tools. Imported tools default to 'unknown' + destructiveHint: true.
  • annotations.cancellable (optional boolean) declares that the HANDLER honours the hard abort signal the caller forwards and returns promptly. It is a promise about the handler, not about the dispatch: a declaring tool must be bounded by construction, because the caller then awaits it to settlement with only the trust tier's execution ceiling as a backstop. Undeclared ⇒ the caller abandons its WAIT after the join grace and reports an interrupted result; the call itself keeps running.
  • askOnKeys (optional string[]) is the per-key approval posture: the ROOT input keys whose PRESENCE makes a call ask. Fail-closed and trust-blind — a third party declares it exactly like a first-party package, because it can only ever ADD an approval prompt. Rules, all checked at load: a non-empty array of at most 8 distinct strings; every element a ROOT property of the tool's own inputSchema (the caller therefore passes rootInputKeys, and a declaration without them is an ERROR, never a skipped check); never a RESERVED_INPUT_KEYS name; annotations.destructiveHint === true; and annotations.category ∈ {write, execute, network, machine} — elsewhere the consent floor asks about every call, so an inert declaration is refused rather than silently ignored. The guard ENGINE stays input-blind: the CALL SITE computes the effective metadata (agent-core's effectiveToolPolicyMetadata), reading PRESENCE and never a value.
  • concurrencySafe (optional, true only) declares that two calls of the tool may run at the SAME time. Requires annotations.readOnlyHint === true and must not sit beside askOnKeys. Declaration only — what a consumer does with it (agent-core runs maximal runs of adjacent declaring calls together) is the consumer's rule, and it widens no gate.
  • x-neuralis.recovery is REJECTED — recovery proof is asked of the brain-core mutation-safety policy at runtime, never declared on the tool.

Open-bag normalisation

validateToolSchema walks every nested subschema after the contract checks pass and injects a default propertyNames constraint on every open object bag (additionalProperties: true and no existing propertyNames). The default pattern blocks prototype-pollution and reserved-namespace keys (__proto__, constructor, prototype, anything starting with __ or $). Authors who need a different shape declare their own propertyNames and the normaliser leaves it alone. The root schema is exempt because the contract requires additionalProperties: false at the root.

App surfaces

Every UI contribution lives under neuralis.app.surfaces[]. Each surface declares kind: 'widget' | 'card' | 'dock'.

Renderer × trust matrix:

| Renderer | First-party | Trusted | Untrusted | Notes | |---------------|-------------|---------|-----------|--------------------------------------------------------| | direct | yes | no | no | Compiled React; first-party builtins only. | | iframe | yes