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/agent-core

v0.3.0

Published

Agent runtime kernel for Neuralis — the nervous system of the AI workforce, and the package protocol's first provider and universal consumer at once: the one runtime that loads every package and executes every element of the contract on its way to the mod

Readme

@neuralis/agent-core

Agent runtime kernel for Neuralis — the nervous system of the AI workforce, and the package protocol's first provider and universal consumer at once: the one runtime that loads every package and executes every element of the contract on its way to the model. Owns the agentic LLM loop, model providers, MCP client management, package loading, the contribution host, conversation persistence, hooks, policies, connectors, the skill protocol, the workflow engine, and the chat surface.

Responsibility

  • Bootstrap the package system: build the kernel registries, load every builtin and installed package, run init / start / onBootstrapComplete, and expose a public API via getPackageApi('@neuralis/agent-core').
  • Run the agent stream: provider selection, conversation assembly, tool execution, policy + hook evaluation, persistence, and SSE delivery.
  • Aggregate contributions from every loaded package (tools, prompts, resources, files, hooks, policies, connectors) into a single MCP-compatible surface.
  • Serve REST routes under /api/packages/@neuralis/agent-core/* for agents, conversations, streams, interactions, models, tools, files, packages, connectors, MCP servers, usage, logs, context-window usage, and read-only agent setup inspection.
  • Expose a generic OAuth 2.1 MCP HTTP server backed by the same contribution host for external clients.
  • Own scheduled/background execution: the workflow/ engine (calendar workflows, scheduler, run queue, creator-identity runs — see the Workflow engine section). The legacy @neuralis/orchestrator package is deleted (modular-meerkat Inc 5); its reserved id stays squat-protected in the host.
  • Own the interactive terminal: PTY sessions, the WebSocket companion mount, the source-tab resolver, and the xterm.js widget (see the Terminal section). The terminal used to be a separate package; it lives here because the host-access plane needs the SAME PTY code, input guard, and uri-policy source index in-container and host-side.
  • Own the host-access plane: the host-resident broker client, the host terminal proxy, and the plane-partitioned shell gate (see the Host access section).
  • Does NOT own filesystem indexing (see @neuralis/brain-core), admin surfaces (see @neuralis/admin), or machine sandboxes (see @neuralis/machine-core).

Public Surface

Exported from index.ts:

  • Bootstrap: bootstrapAgentCore, createAgentCoreState, buildAgentCoreApi, applyAgentCorePostBootstrap, AgentCoreState, AgentCoreApi, InstalledPackageMeta.
  • Runtime: AgentRuntime, loadAgentSystemPrompt, DEFAULT_SYSTEM_PROMPT, RuntimeParams, every Runtime*Event, LLMToolSchema, mapMcpToolToLLM, mapMcpToolsToLLM, buildToolResultBlocks, compactHistory, SeqCounter, buildConversationMap, renderConversationMapSection.
  • Stream: SseStream. Own-stream Stop keeps the client attached through canonical user-row acceptance and server termination; it preserves the submitted bubble and any next draft. Unconfirmed originals are manually copyable in the existing status banner, without automatic draft restoration or resend. presenceBus for project-scoped user-presence pub/sub. Published from the SHARED runAgentStream path (stream/StreamOrchestrator.ts: streaming as the first statement inside the active-stream try, idle in the outer cleanup finally that frees _activeStreams) — NOT the stream route — so BOTH interactive chat turns AND headless workflow runs raise the same watch-mode indicators (Golden Rule #1; there is no workflow-specific presence path). A PresenceEvent.origin ({kind:'workflow', workflowId, workflowTitle}, set by WorkflowRunner via StreamRequest.presenceOrigin) tells consumers the "user" is a headless workflow: the watch-mode banner reads "Workflow '' is running…" and the composer locks even for the workflow's OWN creator (a self-created scheduled run is not them typing — the host hook bypasses the userId !== currentUserId gate when an origin is present). The idle publish also carries a PresenceEvent.outcome ('complete' | 'error' | 'stopped', lifted from the finalize reason; absent on streaming) so a member watching a conversation they did NOT start — another user's stream or a headless workflow — can tell a clean finish from a failure (presence otherwise collapses both into idle). Only the enum travels, never the error text; own conversations use the client store's own error state. The conversation picker + chat tabs surface it via the useConversationStatus hook (app/chat/hooks/) → ConversationStatusDot. Presence is tracked per (agentId, conversationId), NOT per agent (presenceKey in PresenceBus.ts; the client mirrors it with presenceByConversationId): one agent commonly streams MANY conversations at once (every scheduled workflow runs as the agent into its own conversation), so a per-agent last-event-wins slot let a second same-agent run false-unlock every other still-running conversation's lock — the composer lock resolves the WATCHED conversation's own presence (useConversationPresence), while the dock avatar stays per-agent (useAgentPresence). Gotcha (deploy-load-bearing): presenceBus MUST stay a globalThis + Symbol.for('@neuralis/agent-core:presenceBus') singleton (see src/presence/PresenceBus.ts). Next.js evaluates this module several times per process (observed 3× — the /api/events presence channel, the catch-all stream route, and the package loader each get a copy) even though @neuralis/agent-core is in serverExternalPackages. A plain module-level new gives the orchestrator (publisher) a different instance than the /api/events hub (subscriber), so cross-user presence — dock teammate avatars, the watch-mode banner, the live-timeline refresh (#17) — silently never fires. Do NOT "simplify" it back to a bare new.
  • Providers: ProviderType, ModelInfo, StreamProvider, ChatMessage, ToolSchema, ALL_MODELS, findModelById, getModelsByProvider, resolveDefaultModelId (sync, config-aware — replaced the module-load DEFAULT_MODEL_ID const in Inc 2), getProvider, getDefaultProvider, getAvailableModels, getDefaultModelId, clearProviderCache, providerFactory, syncLocalProviders, invalidateLocalProvider, invalidateCredential. Per-provider clients (getClaudeClient, getMinimaxClient) and message converters (toAnthropicMessages, toOpenAIMessages, toGeminiMessages). Codex OAuth helpers: setCodexPersistCallback, setCodexPurgeCallback, setCodexRefreshMutexCallback, revokeCodexRefreshToken, CodexCredentialPurgedError.
  • Tools: ToolCaller, RuntimeTool, RuntimeToolResult, ToolExecutionLikeResult, CompactAction, CompactHistoryInstructions. The runtime tool dispatch goes through StreamToolDispatcher (stream/); the legacy LocalToolManager / CompositeToolManager classes were removed in the move to the file-first kernel ToolDispatcher path.
  • Skill protocol: parseSkillContent, injectVariables, SkillFrontmatter, ParsedSkill (from skill/). The old commands/ invocation system (CommandRegistry/CommandExecutor + the execute tool's command action) was removed — skills replaced model-side command invocation. Package commands/*.json survive only as definition + hosted-MCP-prompt surfaces (prompts/list/prompts/get for external clients).
  • MCP: McpClientManager, McpProtocolClient, createMcpClient, McpSdkClientAdapter, McpError/McpSessionLostError/McpAuthRequiredError, importMcpJson, createHostMcpManager, composeToolCatalog, validateMcpServerUrl, validateMcpServerUrlResolved, MCP entity types, and the mcp/server and mcp/oauth subpath exports. (The legacy McpJsonRpcClient/McpHttpSession client and its context-header helpers were removed — external HTTP servers receive no identity headers; see mcp/README.md.)
  • Hooks: HookRegistry, HookDispatcher, HookExecutionError, HookTimeoutError, resolvePackageHooks, setFirstPartyHookResolver, buildSkillHookCallback, resolveSkillHooks, every Hook*Payload, HookSource, HookRegistration, HOOK_SOURCE_PRIORITY.
  • Policies: PolicyRegistry, PolicyEngine, InputValidator, PolicyDeniedError, resolvePackagePolicies, DEFAULT_TRUST_CEILINGS, buildTrustRules, GUARD_PROFILES, DEFAULT_GUARD_PROFILE, evaluateGuard, isGuardProfile, all policy types.
  • Stores (types only; instances created in lifecycle): AgentRecord, AgentConfig, SystemPromptConfig, ToolsConfig, PackagesConfig, PackageOverride, FileOverride, RuntimeSettings, McpConfig, AgentAppearance, ConversationMeta, StoredMessage, UsageEventRecord, UsageSummary, ListUsageFilter. PackagesConfig is the nested per-agent package/file enablement (S3 gate, store/README.md); the flat FilesConfig is removed.
  • Files: resolveFilesCatalog, resolveDisabledPackageIds (the S3 package gate primitive — agent ∪ conversation deny-list, consumed by injection/skills/tools/overview), buildFileInjectionGroups, buildFileInjectionSections (deprecated bridge), ResolvedFile, ResolvedFilesCatalog, FileInjectionGroups, InjectionContext. On top of S3, the S2 per-contribution feature gate runs at the same boundaries: resolveFilesCatalog / buildPackagesOverview / buildSkillFilesAccessor take the caller's grantedFeatures (request.grantedFeatures on the stream, req.session.grantedFeatures on /files/catalog) and drop every file whose PackageFile.requires.features the caller does not satisfy (meetsRequires from @neuralis/package-system/access). Feature-gated files are hidden, not [OFF]-marked — a member cannot infer they exist, and activating one yields the plain SKILL_NOT_FOUND (no distinct forbidden error). Hosted MCP prompts apply the same predicate to PackageCommand.requires in PackageContributionHost.listPromptsForContext. Authored via the flat requiredFeatures: [a, b] SKILL.md / contribution frontmatter key (PackageFileDiscovery maps it to the first-class requires field; a nested requires: form is unsupported by the minimal frontmatter parser and warns). Package-level non-inference (S2 tail): when feature-gating reduces a package to zero caller-visible surface (no visible tools/commands/files/connectors/hooks/policies) while it does declare gated content, the WHOLE package is dropped — buildPackagesOverview omits its block (and returns '' if every package is gated away) and the packages/overview route filters it out of the payload (so it never appears in the agent Packages editor or the conversation-scope PackageSelector). A genuinely empty or failed-to-load package (no gated content) is kept so it stays visible/fixable; gated tools are already absent from the dispatch catalog (S2b).
  • Packages: PackageDataManager, PackageLoader, discoverPackageContributions, applyDiscovery, resolvePackageDefinitionFromRoot, buildRuntimeDefinition.
  • Domain: provider-neutral conversation types and DomainConversationAssembler.
  • Protocol: MCP-native message and tool-result types under the protocol namespace.
  • Auxiliary (auxiliary/, formerly summarizer/ — all non-chat-stream LLM callers): generateConversationTitle, runSummarizer, summarizeConversation, computeCoverageCutoff, buildHistoryForMode, buildSummaryMarkdown, parseSummarySentinel, recordAuxiliaryUsage. Title, prompt-polish, and summary share ONE dispatcher (AuxiliaryCompletion.ts, on the provider ADAPTERS — pin / inherited / auto selection, thinking-aware request sizing) + ONE usage path.
  • Context: injectAttachmentContextIntoHistory, extractAttachmentTokens, buildAttachmentContextHeader. Attachments use bounded text previews or original image/audio/video blocks; the per-step delivery boundary checks image encoding/signatures and active-model MIME limits, preserving the journal and source file when it emits a recovery marker.
  • Subpath exports: @neuralis/agent-core/runtime (lifecycle), @neuralis/agent-core/mcp/server, @neuralis/agent-core/mcp/oauth, and the two UI subpaths a consumer package compiles against, @neuralis/agent-core/app/chat and @neuralis/agent-core/app/chat/store (the types of the two tier-2 ids below; no other app/ path is exported). The workspace UI ships as ONE runtime module: app/host.tsx (the install…HostComponents entries for chat, calendar and terminal) is built by pnpm build:ui (neuralis-build ui) into dist/app/ (host.js + chunks + host.css, xterm's stylesheet), declared in neuralis.app.module, and attached by the host at runtime; the module publishes @neuralis/agent-core/app/chat and /app/chat/store as tier-2 shared modules (app.module.provides), so a consumer bundle reads this module's instances. The host renders no chat module of its own: agent-core fills the host slots (app/chat/host/workspaceSlots.tsx — the workspace provider with presence and the card-runtime reconcile, the agent-studio empty state, the dock tile's activity). @neuralis/agent-core/app/chat also re-exports the card runtime registry — the single runtime source of truth for every chat card, implemented ONCE in the kernel (@neuralis/package-system/client) and seeded here with the builtin cards. registerCardComponent(type, component, { owner, renderer, mountPolicy, estimateHeight? }) is deny-by-default (owner-checked; a package cannot overwrite a builtin or another package's card; a snapshot iframe cannot shadow a first-party direct card) and returns an explicit CardRegistrationResult instead of throwing; a contributed card may bring its own row-height model (estimateHeight, a module-level stable reference) so the timeline stops guessing its size from a host-side constant; the registry is an observable store (useCardRegistration / useCardRegistryVersion) so a card entry re-resolves on a hot register/replace without a timeline event, and the timeline reads cardRequiresKeepAlive to raise overscan only for genuine keep-alive (iframe / mcp-app) cards. Full contract in app/chat/cards/README.md.

The package definition is declared in package.json under neuralis. Feature taxonomy (S2c C1): core.agents (agent CRUD), core.execute (the act of running an agent — stream/interactions/conversations/context-window; it also gates the execute TOOL itself, and a packaged skill's shell scripts need exec.container beside it), core.observe (read-only introspection — logs view/usage/tools-catalog/files-catalog/packages-overview/mcp-servers/connectors/models), core.web (the two web tools), plus the workflow trio workflow.read / workflow.write / workflow.dispatch (the Calendar/engine surface), channels.connect (member-default since Inc 4: a user's OWN channel connections + pairing approvals — USER scope only), mcp.connect (member-default, MCP1 Inc2: connect your OWN external MCP servers — OAuth flow initiation + self-scope mcp.<serverId>.* token storage via the hardened isMcpSelfScopeCredentialId predicate, and the secret outbound-header values of the same class (mcp.<serverId>.header.<KEY>, MCP5B — see mcp/README.md); project/global MCP secrets stay on the admin Credentials surface), git.connect (member-default, G4 keyring-kestrel Slice 1: connect your OWN git remotes — seed a push token/PAT into your OWN users/<id> scope as git.<host>.pat via the hardened isGitSelfScopeCredentialId predicate + the unified SelfScopeCredentialWriter port's 'git' class; project/agent git tokens stay on the admin Credentials surface. Remote OPS additionally require drive.write + the source's uri-policy exec), credentials.self (member-default, Phase M BYOK: set/remove/list YOUR OWN credentials — catalog-declared + arbitrary custom ids — in your OWN users/<id> scope from the chat Connections section's Credentials group; reserved git/channel/MCP ids stay on their own connect surfaces, project/global secrets stay on the admin Credentials surface behind project.credentials.write — and global, plus any scope outside your own, additionally behind platform.scope, which carries no default grant to any role), core.prompt-polish (the composer prompt-polish rewrite call — /prompt-polish route + PromptPolisher), terminal.read (see and open the terminal widget; the route feature for every terminal/* route — manager/member default, deliberately NOT viewer), plus exec.container (the container plane's shell ENTRY — granted wherever core.execute is, and NOT escalation-only: it is a door, the uri-policy is the regulator), and the escalation-only features that carry no grant below the admin tier — the seeded admin role no longer carries the '*' wildcard, so admin holds each of them through its own ENUMERATED defaultRoleGrants.admin entry in this manifest and owner through '*'; manager/member/viewer get none of them, and any custom role can be granted one: mcp.sidecar (MCP1-S: activate command-based stdio MCP servers to run in a managed sidecar container — arbitrary code in platform infra, strictly stronger than mcp.connect; enforced FRESH at the spawn floor), mcp.apps (MCP1-C: render connected MCP servers' ui:// HTML templates as interactive chat cards — untrusted external HTML in the isolated-origin sandbox; gates all three mcp-apps/* routes), core.logs (platform-log read, escalated inside the logs route on top of its core.observe baseline), core.connectors (create, configure, connect/disconnect, enable/disable and delete a project's outbound tool connectors — escalated inside connectors/handleRoute.ts on top of the connectors/* route's core.observe baseline; READING the connector list, health and tools stays on core.observe), exec.unconfined (bypass the execute-shell URI-policy gate and the OS sandbox — inside the container; it also opens the container plane by itself), terminal.container (the terminal analog: open the Container Root tab, a session rooted at / that bypasses source scoping), terminal.supervise (project-wide managed-terminal listing/control; host sessions additionally require terminal.native), and the two host-plane grants exec.host / terminal.native (let execute and exact host-source terminal tabs reach the operator's machine through the host broker — NOT a bypass: no ROLE can bypass the host's OS confinement — the one relaxation is the operator's ceiling file (confinement: unconfined, single-operator) — and host work is additionally clamped by the operator ceiling; see the Host access section), channels.manage (project-scope channel connections, every connection in the project, cross-user peer links), and core.conversation.fork (branch a continuable fork — manual fork + compacted forks; no grant below the admin tier — enumerated for admin, '*' for owner; grantable per role — enforced in-handler in the shared conversations/* route family). Default grants: admin = every id in this taxonomy ENUMERATED (the whole read/execute tier plus each escalation-only id above — the seeded admin role no longer carries '*'); manager = agents+execute+exec.container+observe+web+prompt-polish+workflow r/w/dispatch+channels.connect+mcp.connect+git.connect+credentials.self+terminal.read; member = the same minus workflow.dispatch (members stream — D1); viewer = agents+observe+workflow.read (read-only, no execute/web/polish/terminal). Role-grant repairs ride ROLE_GRANT_VERSION (host ProjectStore.ts, now 19 — v19 moves every shell entry into the exec.* namespace and derives exec.container for every role that holds core.execute). v10 was the first RENAME step — a third migration primitive beside the additive repairs and the subtractive revokes: it rewrote core.shell.host → core.shell.container and terminal.host → terminal.container on every role of an existing project, builtin and custom, preserving position and de-duplicating (v19 then carried the first of those on to exec.unconfined). Repairs/revokes are keyed by role NAME, so neither could express a rename for a role the platform does not enumerate — a custom devops role holding the old id would have silently lost the grant. '*'-wildcard roles are skipped (the wildcard already covers both ids). providesFeatures entries use the {id,title,description} object form (C9) so the admin Roles UI shows what each feature controls. Trust is first-party. Three widget surfaces are declared: agent-core-chat (auto-opens, frameless, sortKey -1), agent-core-calendar (workflow.read-gated, with a dock companion), and terminal (terminal.read-gated, frameless, multi-instance, with a dock companion). The agent creation studio is deliberately NOT among them — the host renders it directly for the agent_create workspace view, so a widget surface would give one capability two entry points. runtime.hosted exposes a selected tool set over the generic MCP server — web_search, web_fetch, connect, and the connector branch of execute — so external MCP clients (Claude Desktop, Cursor) can exercise them directly. Local shell execution and skill activation through execute still require stream context; prompts and resources stay unexposed.

Bootstrap

The host discovers the manifest's provides: ['runtime', …] provider and loads its file-first src/runtimeProvider.ts export through the kernel. runtimeProvider.boot(RuntimeBootConfig) delegates to bootstrapAgentCore, which owns the loader, registries, private contribution host, hooks, policies and connector lifecycle. It loads the provider first and other builtins sequentially. A builtin that fails validation, init or start — or that the host left out (RuntimeBootConfig.refusedBuiltins: the image build refused it, or it declares a config key the host owns) — is REFUSED alone: its routes, tools and surfaces stay out, the reason is logged once, and health().refused names it under a closed code (load_failed · start_failed · conflict) while the platform runs. whenReady() rejects only when the runtime provider itself fails, when a REQUIRED contract's provider is refused, or when an onBootstrapComplete hook throws; successful initialization alone does not imply readiness.

The host resolves installed package roots through Node module resolution, including real production node_modules roots. It passes only generic kernel config, identity, roots, session/credential ports and HostPorts; product work stays in packages. During init, ctx.config is ready and ctx.hostPorts is available only to first-party packages. ctx.platform.runtimeStack() and .packageUriPolicies() fail before their single publication and return the published snapshots afterward. Bootstrap attaches agent-core's private handles directly; lifecycle context never carries those handles to other packages.

Agent-core implements state construction, API/service adapters and start wiring in the existing src/lifecycle.ts owner:

  • createAgentCoreState(ctx, loader, runtime) installs the config bridge before consumers read settings and starts the existing meta-first conversation reconciliation. The same state.bootReady gates conversation routes, workflow/channel startup, agent deletion and project deprovision. A failed walk retains data and reports failed health; the package loader bounds deprovision waits.
  • buildAgentCoreApi(state) exposes agent stores/services, verified access, connector/MCP snapshots, OAuth and channel connection APIs. getGitRemoteBinding(session, host) returns only the caller's own token-free view: project/user ids derive from the verified session, and the write-capable store is never exposed. Package rescan/cache administration belongs to admin through generic host runtime ports.
  • buildAgentCoreServices(state) implements the session-ticket verifier, agent directory, channel gateway and three OAuth callback adapters. The separate native RuntimeProvider implements the runtime contract. The gateway includes channel descriptors and OAuth start; callback adapters verify the real session and use the package-owned token services.
  • attachAgentCoreSubstrate wires private runtime handles; applyAgentCorePostBootstrap reads published facts and starts existing owners. Structured health reports conversation boot as starting, ok or failed without disclosing exception details.

<runtime_stack> system-prompt block

When streamDeps.runtimeStack is set, the orchestrator calls renderRuntimeStackSection(stack, projectId, sources, areas) (in runtime/stack/) once per stream and threads the result onto RuntimeParams.runtimeStackSection. AgentRuntime.buildBaseSystemPrompt injects it directly after the <workspace> block — between "who you are" and the package overview. The block is byte-stable across agentic steps, so it lives in the cached static prompt and adds zero per-step cost. Contents, in order:

  • host: — os, docker, wsl(distro), node flags
  • path roots: neuralis_home, app_root, projects_root, project_root, cwd — container view first, host-side path in parens when NEURALIS_HOST_HOME is set
  • sources (…) — the per-source connector table, STRUCTURE only: one block per source the caller can address via fs_* (<uri> connector=<k> os_uri=<path|∅ (reason)> [container=<path>] + policy=<r/w/x> [— description] [pkg: id]). The volatile on/off state and index counts render into <workspace_live> instead (renderRuntimeStackSourceLive) — never here, or a sync between turns would bust the cached prefix
  • data_areas (…) — one row per VISIBLE package that declares a manifest dataLayout: data://<zone>/{a,b,c} for package-level directories and data://<zone>/<agentId>/{…} for per-agent ones, under the stream's own agent id (a delegate child renders under its PARENT's id — it runs inside that zone). The rows are collected by contributions/dataAreas.ts (collectDataAreas) from manifest data alone, so they are as cache-stable as the source table; the package set is the overview's OWN visibility decision (visibleOverviewPackageIds — scope/feature-hidden and fully-gated packages omitted, S3-off packages omitted); the zone root is sanitizePackageId (@neuralis/* → bare slug, an instance URI → its mirror runtime/<scheme>/<leaf>, a plain npm id → runtime/_npm/<id>); every name is folded (foldPromptTag) and each row is capped by the runtimeStackAreasMaxChars platform key (default 320). {a,b} is notation for the model, not a glob. The same collector feeds the agent_setup inspector, so the inspector shows exactly the rows the model gets (runtime/stack/__tests__/runtimeStackSites.test.ts pins the three call sites — parent stream, the ONE delegate-child builder for both spawn planes, the inspector)
  • scheme_map (reference…) — fixed builtin schemes with no live source row (app://, neuralis://); discovery-only, not callable until attached
  • mount_table — every NEURALIS_MOUNT_<key> mount as a <key> → <container path> [ro] (host: …) label; a sibling NEURALIS_MOUNT_HOST_<KEY> env carries the host-side path. Labels, not URIs: no connector serves an os:// scheme; to make a host path agent-callable, attach it as a local-connector source with a real URI prefix (pnpm neuralis:mount add … --slug <name>)
  • backing: — Qdrant URL, Ollama URL + embedding model, brain infra mode, the loopback API base — URLs only, no API keys. A max_steps= entry is deliberately NOT rendered: the kernel field behind it reads only the boot-frozen MAX_AGENT_STEPS env var (never the platform-config file override), and this block is cached per-project while the real budget is per-agent and per-run. The enforced step ceiling reaches the model as the volatile <session> Steps: <step>/<max> line instead (runtime/todos.ts getSessionForPrompt, fed by the agentic loop's own counters every step)

The package declares its complete per-agent usage/** directory as machine-written metrics, including events, counters and their bookkeeping. New data sources exclude it through the kernel metrics seed; existing explicit source filters remain owner-controlled.

uriPolicies aggregation

bootstrapAgentCore walks every loaded package, resolves the <self> placeholder against each package's slug (agent-core, brain-core, …) and publishes the resolved snapshot through ctx.platform.packageUriPolicies(). Brain-core reads that shared snapshot lazily; BridgeService.createSource then unions every package's matching baselines into the persisted source-config JSON at first attach. Brain-core no longer hardcodes foreign-package URI patterns — every per-package carve-out (e.g. data://agent-core/$self/** self-write override, packages://<pkg>/src/** read-only) lives in the contributing package's manifest.

Agent-core's own manifest declares the per-agent isolation rules under data://<self>/** and packages://<self>/**. The validator (validateManifestUriPolicies in @neuralis/package-system/policies) rejects foreign-namespace patterns at manifest load time.

Conversation visibility/continue (#17, multi-user) rides the SAME data source policy: ConversationPolicyGate (conversation/ConversationPolicyGate.ts) evaluates data://agent-core/<agentId>/conversations/<convId>/ for read (history-list + GET-read routes) and write (ensure/continue + edit/undo/rewind/retry), with $self = the conversation's agent and ConversationMeta.userId as the policy's resourceOwnerUserId ($owner; $creator is never the conversation creator — it is the SOURCE's createdBy). The shipped baseline makes a conversation its CREATOR's: */conversations/** is read- and write-denied, re-granted by $self/conversations/** users:['$owner'] and by the owner/admin role rows, so another member neither lists, reads nor continues it (a former participant included); the composer's conversations/_staging/** stays read+write for every member (uploads before a conversation exists). An owner widens it in the source's policy editor (a users or roles grant row) with no code change. The fs and shell planes ask the same question through the kernel resource-owner port: conversation/conversationOwnerResolver.ts answers it from meta.json (fork-aware — a fork is its forker's). The gate is fail-closed (deny when the data source is absent; an unknown owner never matches). The owner (meta.userId) stays fixed; a continuing owner/admin is appended to ConversationMeta.participantUserIds (server-populated, drives the dock/picker avatar stacks).

Chat-stream finalization (live guard · forks · non-destructive summarize)

The chat stream's three coupled behaviours, all preserving messages.jsonl as the full, append-only truth (the UI shows it whole, the agent can fs_read it):

  • Live guard profile. The active guard is no longer frozen at stream start. StreamOrchestrator._activeStreams is a Map<streamKey,{guard}>; a guardProfileProvider closure is threaded into the policy path so PolicyEngine.evaluateGuardProfile reads holder.guard at EVERY tool decision. POST /conversations/:id/guard (setStreamGuardProfile) flips it mid-stream — effective at the next tool decision, no approve/deny round-trip. evaluateGuard only ever decides allow-vs-ASK — its 5 floors are PROMPT floors (UX), not security floors. auto ZERO-PROMPTS: it short-circuits to allow at the top of evaluateGuard (so a mid-stream flip to auto removes every prompt), while the REAL deny stays in PolicyEngine.checkToolCall arms 1-5 (blocklist / trust-ceiling / capability / allowlist / feature-gate), which run before the guard arm and clamp auto to the caller's actual permissions. safe/balanced apply the floors. All three profiles (incl. auto, framed for long/full-control autonomous work) carry a guardOverlayFor system-prompt block.
  • Forks. The manual Fork action / manage-conversations skill branches a first-class fork (createFork, kind:'manual') instead of rewriting history; the parent is untouched. Forks nest under the root (conversations/<root>/forks/<composite>/), are continuable, get their own SUMMARY.md, and createFork never leaves an empty stub dir (seed computed before mkdir; cleanup on failure). Gated by core.conversation.fork. (NOTE: compact_history no longer forks — see below.) See store/README.md for the addressing.
  • Non-destructive compaction. compact_history does NOT fork or rewrite messages.jsonl. The model's compact_history tool_use IS the record; the per-row trim (remove / summarize / truncate) is re-applied at prompt build (applyCompaction) + in-memory within the turn, addressing tool outputs by their ⟦tool_use_id⟧ handle (or whole rows by #seq) from the ## Conversation map section (a Markdown heading in the per-step reminder, not an XML block). The tool_result is just "success"; the conversation keeps the originals. Reversible via the meta.compactionMode toggle ("Show full" / "Apply") on the timeline marker bar.
  • Non-destructive summarize (v2 revision store, one owner). ConversationSummaryService (conversation/) is the SINGLE application owner — the auto stream-trigger (source:'auto', occupancy ≥ effective threshold, autoPaused/divide-by-zero guarded), the manual "Summarize now" route, and Learning (same route, activate:false) all call it. A committed summary is an immutable v2 revision under conversations/<id>/_summary-revisions/<uuid>.md; meta.summary (schemaVersion:2, activeRevisionId+contentSha256+coveredHistorySha256+ coveredUntilSeq) is the durable pointer and SUMMARY.md is only a regenerable projection (see store/README.md). The raw conversation files (messages.jsonl, meta.json, SUMMARY.md, legacy summary.md, _summary-revisions/**) are write-protected by an immutable uri-policy floor in the manifest — even the owning agent and owner/admin can't fs_*-write them; only the internal store can (plan.md stays writable as the positive control). Routes: POST /conversations/:id/summarize (closed body activate?/focusInstruction?; refused 409 stream_active mid-stream), POST …/summarize/cancel (UI "Stop"), GET …/summarize/status (read-gated), and POST /conversations/:id/history-mode = the single setReplayMode "Show full" / "Resume summary" toggle. List/get carry a response-only active flag. The former writeSummary/setSummaryMeta/ setHistoryMode writer APIs are removed. See auxiliary/README.md + store/README.md.

Other kernel ports used:

  • PackageInitContext.data — agent-core's process-global state (the system.jsonl log, the codex usage snapshots, the pending MCP/git OAuth flows) lives in its PLATFORM home data.globalDir (~/.neuralis/app/data/agent-core/, first-party packages only, read through requireGlobalDir); connector configs stay in the project data/agent-core/; per-project agent/conversation/usage data is rooted at data.projectsRoot.
  • PackageInitContext.credentials (CredentialResolver) — wired into the loader for every package; packages call resolveCredential instead of reading process.env. (resolveForKind is a dead stub the host answers with {}; there is deliberately no resolveEnv.)
  • PackageInitContext.credentialUse (CredentialUsePort, credential-use limits v1) — the host's per-credential calls/period counter/gate, threaded the same way (RuntimeBootConfig.credentialUsePort → PackageLoader.setCredentialUsePort). The host already counts EVERY resolve at its own choke point (the wrapped resolver, both ConfigProvider getCredential closures, mcpTokenStore.readScoped) — a use-limit deny makes resolveCredential return undefined (audited credential.use_denied), so a denied credential surfaces as the consumer's ordinary "not configured" error. A package calls the port only where resolve-counting is dishonest for its call pattern (brain-core's construction-captured embedding key is the canonical case). Honest units this floor implies: web-search counts RESOLVES (≈2× executed keyed searches — the broker's isConfigured pre-flights resolve too); channel secrets count at send/verify time and a POLLED channel (Telegram) consumes its window continuously; skill shell-env secrets count once per declared id per activation; MCP-sidecar secrets count at readScoped grant time. LLM provider keys are EXCLUDED in v1 (derived from the first-party category:'llm' declarations — the USD spend limits govern those).
  • Declared credentials. agent-core's manifest declares its host-known platform keys in neuralis.credentials[] (PackageCredential[] = { id, label, category?, oauth? }): the 8 llm.* provider keys, llm.openai-codex.oauth (oauth: true), the two web-search keys (websearch.tavily/websearch.brave), the four git-host OAuth App pairs (<host>.oauth.clientId/clientSecret for github, gitlab, bitbucket, codeberg) and the WhatsApp Meta-App pair (oauth.whatsapp.clientId/clientSecret) — 21 in total. Two things are deliberately NOT declared: the six bare channel ids (telegram.botToken, telegram.webhookSecret, the four whatsapp.*), because ChannelEngine only ever resolves the per-connection derived form <baseId>.<connectionId> — a bare row would do nothing when filled, so the admin catalog gets the real derived ids from AgentCoreApi.listProjectChannelCredentialIds(projectId) instead; and any env-backed infrastructure key, which belongs in .env.
  • AgentCoreApi.collectDeclaredPackageCredentials() (mirrors collectDeclaredSkillCredentials()) walks every loaded package's credentials[] and de-dupes by id, feeding the admin buildCatalog package tier. It has no visibility trust filter — a third-party package must be able to surface its own id or an owner could never set it — but it does gate ATTRIBUTION: a first-party declaration wins the label/category/oauth seed whatever the load order, a non-first-party declarer of an already-first-party id only joins declaredBy, and declaredByTrust carries the tier to the UI. The complementary floor is in the kernel: validateManifest rejects a reserved self-scope id (git / channel / MCP class) from any non-first-party manifest.
  • The RESOLVE-side id maps (CREDENTIAL_MAP, WEBSEARCH_CREDENTIAL_IDS, NATIVE_SEARCH_CREDENTIAL_IDS, brain-core's EMBEDDING_MODELS[].credentialId, plus an explicit host-read set) are pinned to the manifests by the credentialDeclarationDrift test — bidirectionally, both sides derived. A resolve-side id with no declaration fails loud (an owner could never set it); a declaration with no consumer fails just as loud (an owner fills it in and nothing happens — that is how the unread qdrant.apiKey row survived for so long).
  • Declared config settings. agent-core's manifest also declares its platform-config keys in neuralis.configSettings[] (PackageConfigSetting[]): runtime (maxAgentSteps, the maxAgentsPerProject seat ceiling, defaultTemperature/defaultModel/attachment + composer caps), workflow, summarizer, websearch/web, provider advanced timeouts + base URLs, localLLMs (json), the five terminal tunables (terminalMaxSessions, terminalScrollbackLines, terminalExecTimeoutMs, terminalMaxOutputBytes, terminalIdleTimeoutMinutes — declared here since the PTY moved in), and the ten host-plane keys (hostBrokerEnabled, hostBrokerSocketPath, hostBrokerUrl, hostBrokerProbeTimeoutMs, hostBrokerProbeTtlMs, hostBrokerProbeFailureTtlMs, hostBrokerFsTimeoutMs, and the three background-host-shell keys hostShellBackgroundLifetimeMs, hostShellPollMs, hostShellStatusWaitMs). AgentCoreApi.collectDeclaredPackageConfigSettings() collects every loaded FIRST-PARTY package's configSettings[] (trust-gated, unlike the display-only credentials collector — envFallback projects env VALUES into admin-visible config, so non-first-party declarations are skipped with a warn) and the host registers them into PlatformConfigStore at bootstrap, before the ConfigProvider injection. The configDeclarationDrift test pins the exported AGENT_CORE_CONFIG_KEYS const (config.ts — EVERY config read must be listed there) ⟺ the manifest, bidirectionally. Inc 2 re-routed the six HALF-WIRED keys through live reads (getConfigNumber/String/Boolean exports): defaultModel (resolveDefaultModelId(), registry default-model thunk), summarizerTimeoutMs, websearchProviderNative (boolean semantics — "true" now enables, deliberate fix), websearchSearxngUrl, summarizerPreserveTurns (the auto-summarize tail — was an orphan, wired), and webToolsAllowedHosts via the host-injected kernel hook setOutboundAllowedHostsSource (package-system web-common) — admin edits now apply live. Inc 5 added 69 tier-3 rows (delegate/retry/media/shell/summarizer-token/websearch-research/workflow tunables — the full sweep) INCLUDING the kernel-consumed keys agent-core declares as their owner (20 today): 11 ride the host-wired setKernelTunablesSource typed hook (the four webFetch* + webToolsUserAgent + the five packageLog* — the level, its per-package packageLogLevelOverrides map and the three rotation knobs — + mcpStdioRpcTimeoutMs — declared here, read by the kernel, pinned by the drift test's KERNEL_TUNABLES_WIRED set, deliberately NOT in AGENT_CORE_CONFIG_KEYS; webToolsUserAgent is the first NON-numeric field on KernelTunables, so the bootstrap wiring carries a tunableStr sibling that reads an empty value back as unset) and 9 ride option seams agent-core passes (toolResult caps ×3 sites, rankSearchResults freshness, dispatcher trusted-tier options + factory cacheSize at PackageLoader, workflowDefaultRuntimeMinutes).
  • PackageInitContext.sessions.createProjectFactory — exposed by the loader; agent-core does not currently consume it.
  • agent-core's app.surfaces[] declares three direct-renderer widgets (agent-core-chat, agent-core-calendar, terminal) plus two dock companions; the host renderer resolves each widget through its CLIENT-side PackageHostRegistry, which agent-core's install…HostComponents entry points fill (the server-side loader keeps no component registry).
  • Cross-package: agent-core resolves @neuralis/brain-core with loader.getPackageApi(...) for filesystem reads (skill discovery, source contributions, file revert).

Security & scope

  • Every route under src/routes/ declares a feature; the 34 route modules are gated by ten distinct ids — core.observe, core.execute, core.agents, workflow.read, terminal.read, git.connect, mcp.connect, mcp.apps, core.prompt-polish, credentials.self; the kernel RouteDispatcher enforces grants before invoking handlers. No route is feature-less (C2 — the former ungated models route now declares core.observe; the repo-wide featureAudit test HARD-fails any route that exports a pattern without a feature). Nine of those modules carry an in-handler ESCALATION on top of their baseline, each declared via export const escalations — logs → core.logs, connectors → core.connectors (every mutating /connectors/* arm; the reads stay on the module's core.observe), plus channels, conversations, interactions, mcp-servers, terminal-sessions, terminal-sources and workflow. __tests__/routes/escalationDrift.test.ts is the drift guard: it fails on a gate the module does not declare AND on a declaration nothing enforces, follows connectors.ts into its delegated connectors/handleRoute.ts, and proves both arms can redden against synthetic defect sources. It deliberately cannot see per-arm coverage — that stays each module's own behavioural suite.
  • Codex subscription usage signal. The codex-usage route (core.observe, read-only) returns the last-known Codex (ChatGPT OAuth) usage-limit snapshot for the caller's resolved credential scope. It keys by codexScopeKey({userId, projectId}) from the verified session only — the usage lane deliberately carries no agent axis (the snapshot is a workspace signal, not a per-agent one), so ?agentId is IGNORED and there is no forged-?agentId surface. The snapshot is written best-effort by the StreamOrchestrator when the codex provider emits codex_rate_limits* meta (parsed by providers/openai-codex/rateLimitMeta.ts, persisted to CodexUsageStore — project snapshots under the project data dir, user/global under the platform package data dir). It surfaces in the chat config "Usage Signal" bar (always-on, both windows, labelled "not accurate") and, mid-stream, as a threshold-gated StreamStatusBanner notice (flashes at 80/90/95/99%, auto-dismiss). Each window's 5h/wk label sits inside a reset-countdown ring that fills as the window nears its refresh (codexWindowReset(window, now) in rateLimitMeta.ts, pure + tested; hover for the exact remaining time). The provider README owns the meta-contract detail.
  • Codex subscription savings. Because codex models are subscription-billed their catalog pricing is {0,0}, so every codex usage row settles at $0. The figure counts codex tokens only. It used to be inflated by any delegate child that ran a paid model under a codex parent: the child's steps landed on the parent's codex row, so computeCodexEquivalentUsd priced Opus tokens as GPT-5.6 and reported the result as money saved. Per-model partitioning ends that — the child settles its own paid row, findCodexPaidEquivalent returns nothing for it, and the savings number shrinks to what it always meant. Forward-only, deliberately: there is no backfill, so rows written before the partition keep the attribution they were written with and a long-lived install's all-time savings still carries some delegated work. providers/usageCost.ts prices the equivalent OpenAI API cost of the same tokens — findCodexPaidEquivalent matches the paid provider:'openai' entry sharing the codex apiModelId, computeCodexEquivalentUsd prices the token counts against it (reusing computeModelCostUsd, the extracted body of UsageMeter.estimateUsdCost — ONE cost-math source for the live meter AND read-side aggregation). Both row writers (UsageMeter and the auxiliary recorder) STAMP that figure on a subscription row as equivalentCostUsd at write time, and every reader goes through resolveCodexEquivalentUsd — the stored value when the row carries one, else the read-time computation (rows written before the stamp) — because the catalog deletes rows: a removed codex row or paid twin computes to 0 and would erase the savings already booked against it. UsageStore.{aggregate,summarize,summarizeProject} carry it as equivalentCostUsd / totalEquivalentCostUsd; GET /usage annotates each codex event with equivalentCostUsd and carries the pricingBasis disclosure on BOTH arms (the agent-less project-summary branch too, since that is the branch the inspect-agent-core usage script reads); the chat-config per-model rows render it in an accent colour instead of $0. Every TOKEN-PRICED figure is a catalog list-price estimate — UsageStoreImpl.pricingBasis carries the one server-authored sentence (providers/usageCost.ts USAGE_PRICING_BASIS) that says so. A plan FEE is not one of them: openai-codex/planPricing.ts estimates it from the reported plan tier, and the savings card renders it beside the note. FOUR surfaces render the sentence verbatim — the chat config Usage Signal card, the admin usage and Codex-savings cards, and the Limits paragraph; the conversation picker's per-conversation spend and the delegate Done-row cost render money with no disclosure, and the delegate row's hover title carries a short local restatement (list price, model steps only) rather than the sentence. A usage event is one PRICING PARTITION of a turn (see stream/README.md), so a turn spanning two models is two events sharing one requestId — a limit is a row budget, not a turn budget. The chat-config panel asks for 2000, which is ample today; no production reader joins or dedupes on requestId, and nothing outside tests reads a row's steps. AgentCoreApi.listCodexSubscriptions() enumerates the connected subscriptions (one per snapshot scope) with planType + estimated monthlyUsd (openai-codex/planPricing.ts, owned by neuralis-provider-maintenance) + the rate-limit windows — consumed by the admin dashboard's per-subscription savings + break-even surface.
  • Agent CRUD is further constrained by agents/agentAccessPolicy.ts ('*' | 'own' | 'view' × project agentOwnership). The own branch accepts two kinds of ownership evidence — an agentOwnership[id] record, and, when there is none, the agent's own persisted creator (userId) — because no create path writes an ownership record, so for a new agent the creator field is the only evidence there is. The record is consulted FIRST, so an explicit assignment always wins over the creator fallback. Four of the five sibling predicates already accepted the creator; canAccessAgent could not, because it took a bare agentId — see agents/README.md for the twelve call sites and which of them deliberately keep denying.
  • DELETE /agents/:id emits an agent.delete audit event. The route is reachable by the packaged manage-agents skill's DELETE agents/<id> call line as well as by the chat panel's Agent Config card, so an agent deleting an agent leaves a record of who did it. The admin widget deletes through its OWN agents-admin/:id route and audits there, so the two paths are disjoint and one deletion never produces two rows. For a caller the gate REFUSES, the handler answers ONE generic 403 whether or not the agent exists — deliberately, so a refusal cannot become an existence oracle for agents in other members' scopes. (A caller the gate ADMITS is answered on its merits: because AgentStore.delete is rm -rf --force, a delete aimed at an id that is already gone returns 200, which is what lets an operator clear a leftover ownership record.)
  • A per-project seat ceiling (maxAgentsPerProject, default 100) bounds every create path per item, with the batch route refusing a straddling batch whole rather than partially. Refusal is 409 naming the limit. An in-process caller with no access context (system provisioning) is exempt, so a future provisioner could seed a team of any size — but state it precisely: every agent create today reaches the service through src/routes/agents.ts under a real session, so every create that exists today is bounded. The carve-out is a forward-looking allowance, not a live path. It is a tenant-facing tunable, not a security floor. The per-item check NARROWS the concurrent-create window rather than closing it (the count and the write are not serialized); the bounded overshoot is accepted.
  • Agent appearance. The config.appearance arm of PATCH /agents/:id is validated by src/usecases/agentAppearance.ts: an icon from the platform icon library (stored by its library name), a #rrggbb colour, a picture as a base64 data: URL whose BYTES decide its type (PNG/JPEG/WebP — SVG and every document class refused) under the kernel APPEARANCE_IMAGE_MAX_BYTES; an unknown key is a 400. Only that arm is validated — the rest of a config patch keeps the agent service's own contract. The picture lives in the agent's own appearance/<sha256>.<ext>, the record keeps {hash, mimeType, updatedAt}, the replaced file is deleted, and list/get answers carry a server-derived imageUrl (with ?projectId=, so the host never builds an agent-core path). GET /agents/:id/appearance-image/:hash serves it behind core.agents + the agent read gate, re-hashing and re-sniffing the bytes on every read, nosniff, an immutable cache (a member whose access is later revoked keeps what their browser already cached), and one 404 for every failure. Agents list in one deterministic order: oldest first, then by name.
  • Role-priority gate on provisioning. Independent of the CRUD capability check, an agent's teamRef.role is a role assignment and is gated by canAssignAgentRole (agents/agentAccessPolicy.ts, backed by @neuralis/package-system/access rolePriority). A caller may only stamp an agent with a role whose priority is >= their own (a member cannot mint an admin/owner agent) — enforced at the single AgentService.createFromTeamMember choke point (covers POST /agents from-team and POST /agents/batch), plus the plain-create config.teamRef.role smuggle guard and the PATCH /agents/:id teamRef.role rewrite (→ 403 role_priority_forbidden). The caller's priority rides on SessionContext.priority; a system session (no priority) bypasses.
  • Stream and tool execution honour PolicyEngine (deny / approval-required / allow), package trust ceilings (first-party | trusted | untrusted), input validation against the tool's JSON schema, and PreToolUse hook decisions in priority order admin/managed > project/host > user/skill > package.
  • Tool feature-gate (S2b). A tool's requires.features (x-neuralis.requires) is the ONE declarative source of its feature gate — resolvePackagePolicies compiles each into a feature-gate rule (the legacy manifest policies[] kind:'access' compile path is removed). PolicyEngine.checkFeatureGates enforces it on EVERY dispatch (ToolExecutor) and re-evaluates it on approval replay (it sits before the guard-profile block, so a feature-deny is always deny, never approval-required). The check uses the shared hasFeature — no inline includes copy. For model visibility, StreamOrchestrator runs PolicyEngine.filterVisibleTools over the per-stream catalog and folds the deny complement into the same unavailable-name set the S3 package gate uses, so a feature-gated tool is absent from the catalog (hidden ≠ [OFF]), and a hallucinated dispatch is rejected with a generic, non-leaking Tool not available: <name>. The host client snapshot's buildTools applies meetsRequires too (3rd surface). machine-core keeps an in-handler escalation on top of the declarative baseline (its README) — that two-tier shape (declarative baseline + input-dependent in-handler hasFeature, declared as requires.escalations for catalog/lint) is the sanctioned pattern.
  • PendingInteraction records replay only the persisted validatedInput (verified against validatedInputHash); approve/deny callers cannot inject replacement tool args.
  • Hosted MCP requires either a per-agent API key (resolves to that agent's userId/projectId/agentId) or an OAuth JWT bound to the same scope; the __system__ sentinel is rejected on every auth arm (assertNotSentinel; SENTINEL_SET holds exactly the three __system__ values). __skill__ is an audit principal deliberately outside that set and is refused on its own path instead — executeSkillAction's explicit === SKILL_USER_ID chain check.
  • The MCP HTTP server validates URLs and rejects header-based scope override (x-user-id / x-project-id / x-agent-id).
  • Principal revocation closes live state; it never decides access (src/usecases/revokePrincipal.ts, registered as agent-core's onPrincipalRevoked). A disabled, deleted or signed-out user — or one removed from a project, or whose project was archived, then for that project only — is already refused everywhere because the host produces no session for them; this closes what they opened before. Every reason: the principal's external MCP sessions on :3101 close and its MCP OAuth refresh tokens are revoked (mcp/server/companion.ts revokePrincipal), running turns stop through the holder's HUMAN stop (stopStreamsOwnedBy, never the drain's abort, which would finalize an auto-resumable shutdown), background delegates stop, detached shells are killed (killShellsOwnedBy), PTY sessions are destroyed with every attached view told exit, which closes its WebSocket (PtySessionManager.closeAllFor), the user's HOST-plane terminals are destroyed through the broker's own DELETE /pty/sessions/:id (hostTerminalDestroy), whose close the :3101 proxy follows, and every terminal view the user holds as a VIEWER closes on both planes (closeTerminalViewsFor — a supervisor's view of another user's PTY included; that PTY and its other viewers stay) — an unreachable broker is logged and its PTYs wait for the broker's idle reap, never re-attachable because every attach re-authenticates. Every reason but sessions_reset also stops the user's own Telegram pollers (in the one project for a membership removal or archive); the resync asks the ONE host isPrincipalActive(userId, projectId) port, so they stay down while the user is inactive or no longer a member there and restart after a re-enable or a re-add. deleted alone sweeps: the user's own channel connections (removeChannelConnection, the DELETE route's own usecase), the MCP API-key hashes of the agents they created, their git bindings, and every ACTIVE workflow they created is PAUSED (creator_deleted, never deleted) with the list in the principal.live_state_closed audit row. The MCP session and refresh-token closes are host-called (MountResult.closeSessionsFor, FileOAuthRepository.revokeAllForUser).
  • Tool result bodies are size-capped through the kernel boundToolResult helper (20 KB on content + structuredContent; _meta is bound separately and never serialized to the LLM).

Identity files (self-improvement substrate)

Each provisioned agent owns three identity files under data://agent-core/<agentId>/identity/:

| File | Volatility | Role | |---|---|---| | SOUL.md | stable, living | Personality and role posture plus a few direct links to memories that currently shape durable craft. | | USER.md | slow, living | A curated, person-scoped set of direct preference/context links; add, replace, or prune after meaningful learning. | | HEARTBEAT.md | hot, living | A small resume index of current plans, commits, reports, evidence, obligations, and next checks; replace stale links rather than append history. |

The files stay thin but self-evolving. They may link directly to several focused memories or artifacts; no mandatory master-index file sits between identity and memory. Routine facts remain in their owning memory/plan/commit/report/source/README, while identity only curates the links that currently affect persona, collaboration, or resumption. A meaningful task or workflow review may update those links; trivial work need not rewrite identity.

The seed templates ship under team/<slug>/identity/<FILE>.md and are copied into data/agent-core/<agentId>/identity/ at agent provision time. Provision-time substitutions: {{agentId}}, {{agentName}}, {{role}}, {{createdAt}}, {{packageId}}, {{memberSlug}}, {{projectId}}, {{userId}}. At stream injection time InstructionInjector additionally substitutes the runtime {{conversationId}}, {{agentId}}, {{projectId}}, {{userId}} placeholders inside any package-file body — use the flat names only; dotted forms like {{user.id}} are NOT supported. The agent rewrites identity files in place through fs_write against data://agent-core/<agentId>/identity/<FILE>.md. Priority order in the system prompt is SOUL → USER → HEARTBEAT, then any extra *.md files alphabetically. Priority matching is case-insensitive so legacy lowercase identity files keep resolving to the same slot.

Prompt-injected instruction & rule files (the always-on floor)

agent-core authors its own always-on system-prompt contributions as package files (discovered from the instructions/ and rules/ folders — category is derived from the source folder, never an authored field). The set was consolidated from nine narrow files into two dense, always-on files; the detailed how-to now lives in matching skills (context-management, plan, execute-skill, delegate-skill) so the prompt floor stays slim while the method stays discoverable.

| File | Category | Carries | |---|---|---| | instructions/neuralis-kernel.md | instruction | Platform model, initiative and craft floor, live-capability discovery, session/memory ownership, source pointers and language. Selective-compaction method lives in context-management; filesystem and tool mechanics live in their owning rules. | | rules/tool-use.md | rule | Schema-valid calls, recovery, approval boundaries, auditability and the execute security floor. Filesystem mechanics and path/scope rules live in brain-core rules/brain-rules.md. |

These are injected every turn (activation: prompt-context, pinned, invocation: always), so a behavioural FLOOR must live in rules//instructions/, never only in a skill body — a skill that is never activated (especially a headless workflow run) would otherwise lose the floor entirely.

The seven base subagents (agents/*.md)

agent-core ships the platform's universal delegation roles. delegate resolves an omitted agent to general.

| Agent | The question it answers | |---|---| | explore | Where does something live, in how many places, or does it exist at all | | research | What is true outside this workspace — an API, a version, a price, a convention | | planner | What is the step sequence, and is it the right one | | execute | Carry out agreed work end to end | | validate | Is this plan, change or artifact sound | | operate | Does the running system actually behave as claimed | | general | Anything that fits no sharper role — the default |

They chain on roles do not overlap: whoever plans does not execute, whoever executes does not certify their own work, whoever validates does not fix.

Two properties make them base, and both fail silently if broken — agents/__tests__/baseSubagents.test.ts derives them from the shipped files:

  • No allowed-tools. Each inherits the caller's full (already S2/S3/scope-filtered) tool set, so it reaches package tools, MCP tools and anything the user adds later. A fixed allowlist would blind it to all of them with no error anywhere. Narrowing belongs to the CALL — delegate's tools/blocked_tools/disabled_packages. ABSENT is the load-bearing word: a persona that declares allowed-tools: [] is making the opposite statement and receives no tools at all, which is why the parser keeps the two apart. The honest consequence: a "read-only" base agent is read-only by instruction, not by contract; pass blocked_tools where that must be enforced. Custom agents that exist to be restricted may of course declare their own allowlist — and that list is a CEILING: a call's own tools is INTERSECTED with it, never added to it. A call can narrow a persona, never widen one. The tools list is answered in three states, and the ceiling is only one of the reasons a name falls out of it: every requested name is measured against the child's own catalog first, whatever survives runs, whatever does not is NAMED BACK in the delegate result (not in the child's catalog · barred from every subagent · outside the persona's list · removed by the call's own blocked_tools · delegate at the depth limit), and a list of which NOTHING survives is refused before any write rather than starting a tool-less child. A name outside that catalog is reported in the same words whether it is hidden from the caller or invented — telling those apart would disclose the persona's list. The candidate set is ALSO narrowed by the packages this call disables for the child, which is why the sentence names the SUBAGENT's catalog and not the caller's: "not available to you" would be false exactly there. Both tools and blocked_tools are matched after the same wire-prefix normalization — a denylist that silently blocks nothing is the one direction in which a missed name hands the child MORE.
  • No requiredFeatures. A gated base agent is not a base agent. The four retired brain-core agents (planner-agent/reviewer-agent/scaffolder-agent/brain-search-agent) were package-authoring specialists, not these seven — and three of the four sat behind packages.author, which member and viewer do not hold, so a member saw two of the five shipped agents and nothing anywhere said which two were missing.

Their description is the whole routing surface, and <packages> truncates a non-skill file's description at 80 chars (MAX_FILE_DESC_CHARS) — a longer one is a CUT one, losing exactly the discriminator at the end. The role-picking table lives in the delegate-skill body, where there is room for it.

delegate applies S2 + S3 to agent resolution, not just to the child's tools. An agent body BECOMES the child's system prompt, so which agents exist for this caller is the same deny-by-default question as which files get injected. resolveAgent / resolveDefaultAgent tak