@agent-core

agent-core

The agent runtime: conversations, streams, model providers, tools, MCP, and the chat UI.

Maturity: beta (80 %)

Agent core. One durable agent stream across many model providers, with a sandboxed execute shell, Agent Skills, delegation, approvals, MCP in both directions, workflows, channels, a terminal and limits on every run.

  • Confined shells need a Linux host kernel with Landlock support; without it, lower-trust shells are refused rather than run unconfined.
  • Workflows and channels run in a single process; a second replica would run every schedule twice.
  • Subscription model usage is not metered in money, so money spend caps do not bind it.
  • Container terminal sessions end when the app restarts.

How maturity is measured

@neuralis/agent-core is the runtime kernel of Neuralis. It owns the agentic LLM loop end to end: model provider selection, conversation assembly and persistence, tool execution, policy and hook evaluation, and SSE delivery to the chat UI. It is also the package that loads every other package — the package loader, the contribution host, and the generic MCP server all live here, so every capability any package declares flows through agent-core on its way to the model. That makes it the package protocol's first provider and its universal consumer at once: the one runtime that actually executes every element of the contract, whichever of the three paths a package arrived by.

Responsibilities

  • Bootstrap the package system. A single entry point builds the kernel registries, loads agent-core first, then loads every other builtin and installed package and runs their lifecycle hooks.
  • Run the agent stream. Provider selection, system-prompt assembly (instructions, rules, skills, the <packages> overview, the <runtime_stack> block), tool dispatch, usage metering, and persistence.
  • Aggregate contributions. Tools, prompts, resources, files, hooks, policies, and connectors from every loaded package are merged into one 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 workflows.
  • Expose the OAuth 2.1 MCP HTTP server for external clients (Claude Desktop, Cursor, ChatGPT, VS Code) — see MCP access.
  • Own scheduled execution through the workflow engine.
  • Own the interactive terminal — PTY sessions, the WebSocket companion, source-scoped tabs, and the xterm.js widget.
  • Own the host-access plane — the opt-in, operator-provisioned path that lets a shell command or a terminal tab run on the host machine instead of the container. See security.

It does not own filesystem indexing (brain-core), admin surfaces (admin), or machine sandboxes (machine-core).

Provided features

The manifest declares the feature vocabulary other layers gate on (see features and access):

FeatureWhat it grants
core.agentsView and manage agents (create, configure, assign).
core.executeStart agent streams and run tools — the chat loop, conversations, interactions, context window — and the execute tool itself. A packaged skill's shell scripts need exec.container beside it.
exec.containerOpen a shell inside the app container with the execute tool: the container plane's ENTRY. Granted wherever core.execute is. It is a door, not a bypass — the source's path policy still decides where a command may start.
core.observeRead-only introspection: usage, logs view, tools/files catalog, packages overview, MCP servers, connectors, model list.
core.webUse the web_search / web_fetch tools (outbound web egress). Storing a fetched page additionally needs drive.write.
core.logsRead an agent's per-agent run log under the session project (escalation beyond core.observe; owner/admin by default). agent-core's platform-global system / package logs need platform.audit instead.
core.connectorsCreate, configure, connect, disconnect, enable, disable and delete a project's outbound tool connectors. An escalation beyond core.observe, which keeps the read side (list, health, generated tools). No grant below the admin tier — admin holds it through its enumerated default grant, owner through the '*' wildcard; grantable to any custom role.
exec.unconfinedBypass the execute-shell URI-policy gate and the OS sandbox — inside the container, on any container path (owner/admin by default; grantable per role). It opens the container plane by itself, and grants nothing on the host or on a remote source.
exec.hostLet the execute tool target the host through the operator-provisioned broker. Not a bypass: host commands are OS-confined (the operator's own ceiling file may declare an unconfined single-operator mode — no role can select it) and additionally clamped by the operator ceiling. No grant below the admin tier — admin holds it through its enumerated default grant, owner through the '*' wildcard, and it is grantable to any custom role. The grant alone is insufficient without operator provisioning.
terminal.readSee and open the terminal widget; the route feature for every terminal/* route (manager and member by default).
terminal.containerOpen the Container Root terminal tab — a session rooted at / that bypasses source scoping but stays inside the container. No grant below the admin tier; grantable to any custom role.
terminal.nativeOpen exact host-source terminal tabs through the same broker as exec.host. No grant below the admin tier; grantable to any custom role.
terminal.superviseList, attach to, take control of, and terminate every managed terminal in the project. Host supervision also requires terminal.native. No grant below the admin tier; grantable to any custom role.
core.prompt-polishPolish composer drafts with a one-shot LLM call (translate + rewrite before sending).
core.conversation.forkBranch a continuable fork of a conversation (manual fork + compacted forks). No grant below the admin tier — admin holds it through its enumerated default grant, owner through the '*' wildcard; grantable to any custom role.
workflow.readView workflows and runs.
workflow.writeCreate, edit, pause, and archive workflows.
workflow.dispatchRun workflows now and cancel runs.
channels.connectConnect your own external channel accounts (Telegram, WhatsApp) and approve pairing requests on them.
mcp.connectConnect your own external MCP servers: run the OAuth authorize flow and store the resulting tokens in your own user scope. Project/global-scope MCP secrets stay on the admin Credentials surface.
mcp.sidecarActivate command-based (stdio) MCP servers to run in a managed sidecar container. Arbitrary code executes in platform infrastructure, so there is no grant below the admin tier — the seeded admin role holds it through its enumerated default grant, owner through the '*' wildcard; grantable to any custom role.
mcp.appsRender interactive HTML UIs (MCP Apps, ui:// resources) from connected MCP servers as chat cards. Untrusted external HTML executes in an isolated sandboxed iframe, so there is no grant below the admin tier — the seeded admin role holds it through its enumerated default grant, owner through the '*' wildcard; grantable to any custom role.
channels.manageManage project-scope channel connections and link channel peers to other users (owner/admin by default).
git.connectConnect your own git remotes — paste a Personal Access Token or run the one-click OAuth flow (GitHub, GitLab, Bitbucket, Codeberg) — storing the push token in your own user scope. Project/agent-scope git tokens stay on the admin Credentials surface.
credentials.selfSet, remove, and list your own credentials (catalog-declared and custom ids) in your own user scope from the chat panel's Credentials group.

Default role grants: manager and member get agents, execute, observe, web, prompt-polish, terminal.read, channels.connect, mcp.connect, git.connect, credentials.self, and workflow read/write (managers additionally workflow.dispatch); viewer gets agents, observe, and workflow.read only. The seeded owner role holds the '*' wildcard, so it holds everything. The seeded admin role does not hold the wildcard — it holds an enumerated list, declared by this package's own manifest, that covers every feature above plus the escalation-only ones no weaker role receives: core.logs, core.connectors, exec.unconfined, exec.host, core.conversation.fork, mcp.sidecar, mcp.apps, channels.manage, terminal.container, terminal.native and terminal.supervise. Every one of them is grantable to any custom role.

The two host-plane features — exec.host and terminal.native — carry that same admin-tier grant and no more, and holding one is still not enough unless an operator has provisioned the host broker. See security.

Feature ids name the plane a shell opens on: exec.container, exec.host, and whatever id an exec-capable source's connector declares for itself (the virtual desktop's is exec.machine). exec.unconfined and terminal.container mean "unrestricted inside the container" — both were once called …host, which overstated them: they never reached the host. Existing projects are migrated automatically, including custom roles, so a role that held an older id keeps the equivalent grant.

Cross-package position

Every other package reaches the model through agent-core:

  • The package loader discovers each package's directory contract — tools, skills, instructions, rules, agents, docs, commands, hooks — at bootstrap.
  • The contribution host aggregates hosted tools, prompts, and resources and routes tools/call back to the owning package's lifecycle.
  • The stream runtime is the single injection boundary: package contributions are filtered by package enable/disable state and by the caller's granted features before anything reaches the system prompt.
  • Cross-package calls use typed APIs from getPackageApi() — agent-core exposes its agent store, conversation service, usage store, and stream runner this way, and itself consumes brain-core's filesystem API. Its per-agent usage/** directory is declared as machine-written metrics, so new data sources exclude the complete accounting directory by default; existing configured source filters remain owner-controlled.

UI and hosted surfaces

The manifest declares three widgets: the Chat widget (frameless, singleton, opens by default), the Calendar widget — the workspace surface for workflows (Day/Week/Month/Year views, a live activity rail, and a template browser; visible only to workflow.read holders) — and the Terminal widget (frameless, multi-instance, terminal.read-gated), an xterm.js PTY session rooted in a connected filesystem source. Calendar and Terminal each ship a dock companion.

An empty conversation opens on the Package Constellation: the agent's own icon at the center, and every package it can actually use slowly orbiting it live. Packages group into concentric rings by trust path — first-party installs, sandboxed project drops, and source packages discovered in synced sources — ordered with the smallest group innermost; adjacent rings turn in opposite directions. The view is the permission-filtered truth, not an illustration: each package is a distinct colored icon, clicking one opens a breakdown of its contributions (skills, rules, tools, commands, …), commands and resources insert straight into the composer, and each package can be toggled on or off for just that conversation. Disabled nodes dim in place.

The chat composer includes a Prompt Polish control (core.prompt-polish): one click sends the draft through a one-shot LLM rewrite — translate to a target language and improve it at three escalating levels: translate (a faithful translation, nothing else), gentle (translate, then tidy up and add light structure while keeping your own voice), and full (engineer the draft into a richer, well-structured prompt — explicit asks, surfaced requirements and constraints, an output format and success criteria). Pick any model, or leave it on Auto to use the same model the conversation runs on. The result replaces the draft as an editable preview with one-click undo; at full power the rewriter sees the same permission-filtered package overview the agent would, so it can phrase the prompt around the tools and skills actually available to you — never ones your role cannot see. Over the generic MCP server, agent-core exposes a selected hosted tool set — currently the two web tools (web_search, web_fetch) — so external MCP clients can call them directly; prompts and resources stay unexposed.

Consuming external MCP servers

Neuralis is also a spec-compliant MCP client: agents connect OUT to external MCP servers over streamable HTTP (spec revision 2025-11-25 — negotiated protocol version, optional session id, resumable server notifications with reconnect, request cancellation). Add a server in the chat config panel's Connected MCP section or on the agent's MCP configuration; its tools join the agent's catalog next to builtin package tools. OAuth-protected servers use the standard discovery + PKCE flow: the Authorize button (or the agent using the MCP authorize lane of connect) produces a consent link that a human opens and completes — tokens are stored encrypted in your own user scope, resolved scope-exactly, so one member can never ride another member's authorization.

Not every server speaks OAuth. A remote server can also carry custom outbound headers — the API-key header, tenant selector or API-version pin that server expects — in two separate fields. Non-secret literals (x-api-version: 2026-01) live in the agent's configuration. Secret ones do not: you declare the header name when adding the server, which creates an empty credential slot, and the value is typed into a write-only field on the server row afterwards. The value is stored in the encrypted credential store, in your own user scope, and is read only at connect time — it is never written into the agent configuration, never returned by any listing, and never recorded in the audit trail (which carries header names only). A server whose slots are not all filled is not connected at all rather than connected half-authenticated; it stays visible on the MCP servers rail marked unavailable so the reason is never invisible. Importing a standard mcp.json follows the same rule: header names become slots, header values are never imported.

A few header names are reserved because the platform sets them itself — authorization (that is what the OAuth/API-key ladder above is for), content-type, accept, the MCP session and protocol-version headers, and the standard hop-by-hop names. Those are rejected when you add them, rather than accepted and then silently overwritten on the wire.

Command-based (stdio) servers — the npx/uvx kind from a standard mcp.json — import as dormant entries: Neuralis never spawns a child process on the host. A user holding the mcp.sidecar feature can activate one to run inside a managed sidecar container: a hardened, resource-capped Docker container (no privileges, dropped capabilities, loopback-only publishing) runs the command behind a token-guarded HTTP bridge, isolated per user or shared per project by the server's scope, idle-reaped when unused. Secret env vars (say a git server's token) are never stored in agent config — they live in the encrypted credential store and are injected into the container only at start. Security floors on every connection: outbound URLs are SSRF-validated and DNS-pinned (including every OAuth discovery and token endpoint), no Neuralis identity or internal metadata is ever sent to an external server, in-host stdio servers are refused, and capabilities the server did not declare are never called. Connecting servers is governed by the mcp.connect feature (managers and members by default).

MCP Apps — interactive server UIs in chat

Neuralis renders MCP Apps (the ratified ui:// extension of MCP — MCP-UI and the OpenAI Apps SDK converged): when a connected server's tool declares a ui:// HTML template, the tool's result appears as a live, interactive card in chat. Unmodified MCP-Apps / ChatGPT-Apps servers work as-is — Neuralis announces the standard client capability at connect, speaks the standard app protocol (initialize handshake, tool input/result push, app-initiated tool calls), and hosts the app in the spec's isolated-origin sandbox: the untrusted HTML runs on a throwaway browser origin (a second published port), never on the app origin — so Web Storage, self-hosted subresources, and dynamic apps all render while the platform stays fenced (the sandbox origin serves nothing but the relay shell, and the template's Content-Security-Policy is injected server-side, connect-src 'none' by default). An app calling back into its own server's tools goes through the exact same policy gate as a model-initiated call — hard deny rules first, then the conversation's guard profile, with an approval footer on the card when the guard asks. Rendering is governed by the dedicated mcp.apps feature, which carries no grant below the admin tier — the seeded admin role holds it through its enumerated default grant, owner through the '*' wildcard; grantable to any custom role. An owner who wants MCP connections without arbitrary-HTML rendering revokes mcp.apps from the roles that should not have it; mcp.connect is a separate, member-granted feature.

Tool audiences follow the spec's visibility rules, fail-closed. A server tool may declare which audiences it is reachable from — the model, the app, or both. An omitted declaration means both; an explicit list is honoured exactly, so an app-only tool never enters the model's catalog and a model-only tool is not callable from the app; and a malformed declaration is reachable from neither rather than having its valid entries salvaged. An app resolves its calls only against the app-visible tools of the one server that owns it — never a cross-server aggregate — so a same-named tool on another server can neither mask it nor be reached through it.

The card is a retained view: once rendered it keeps its browsing context while the conversation scrolls, so an app does not reload or lose state when it leaves the viewport, and during a live turn its first render waits for the turn to settle so it appears once instead of needing a manual refresh. Every operation the view performs — a tool call, a resource read, its teardown — is bound to a short-lived, opaque View lease that the host keeps in memory and never hands to the app; the server derives the caller's scope from the lease and re-checks live access on every call, and an approval taken through the card resolves exactly once. The full boundary is on the security page.

Folder map

The pages in this section mirror agent-core's real package folders, so the docs map one-to-one onto the code:

Package folderDoc pageCross-reference
tools/ (13 JSON schemas)Toolspackage-system tools
skills/ (16 bundles)Skillspackage-system skills
workflows/ (3 templates)Workflowspackage-system contributions
agents/ (7 base subagents)Agentscontributions
instructions/, rules/Instructions and rulescontributions
connectors/ (connector lifecycle)Connectorspackage-system connectors
channels/ (Telegram/WhatsApp)Channels—
src/git/ (git OAuth connect)Git remotes—
providers/ (model catalog)Providers and models—
app/ (chat, calendar, agent)App surfacespackage-system App surfaces
src/terminal/, app/terminal/ (PTY backend + xterm.js widget)Terminalpackage-system routes
(cross-cutting)Securitysecurity model

In this section

On this page