Runtime stack and substitution tokens
What packages and skills can know about where they run, and the tokens resolved at activation.
Packages and skills should never guess where the platform lives. The runtime
stack snapshot — built once per process at bootstrap — is the single source of
truth for host classification, container-versus-host paths, bind mounts, and
backing-service URLs. Every consumer reads the same immutable value: the
system-prompt <runtime_stack> block agents see, the path resolvers that
translate container paths to host paths, and any package that needs to know
its environment.
RuntimeStackInfo
discoverRuntimeStack() (from @neuralis/package-system/paths) reads the
environment, OS signals, and a few cheap filesystem checks, and never throws.
The resulting shape is closed — every field is present, and a value that
is not meaningful for the environment is null, never undefined:
| Group | Fields |
|---|---|
| Host classification | osKind, osRelease, osType, nodeVersion, isDocker, isWSL, wslDistro |
| Container path roots | neuralisHome, appRoot (platform-internal zone), projectsRoot, cwd |
| Host path roots | hostHome, hostAppRoot, hostProjectsRoot — populated in Docker when the host home is configured; on native deploys they mirror the container roots |
| Mount table | osMounts[] — { key, containerPath, hostPath, readOnly } per configured bind mount |
| Services | qdrantUrl, ollamaUrl, embeddingModelId, brainInfraMode, maxAgentSteps, apiBaseUrl — URLs and headline knobs only, never secrets |
Docker is detected via an env override or the standard container markers;
WSL via the distro env var or the kernel release string. Mounts come from
NEURALIS_MOUNT_<KEY> env triplets (container path, optional host path,
optional read-only flag), which the platform's mount tooling writes.
services.apiBaseUrl is the loopback base URL the host serves
/api/packages/* on — this is what the ${NEURALIS_API} token (below)
resolves to, so skill scripts call the local service without hard-coding
ports.
Path translation
Two pure functions translate between the container view and the host view using the snapshot:
resolveHostPath(stack, containerPath)— container → host. Checks the app root, then the projects root, then every mount, and returns the input unchanged when no translation applies (native deploys). Matching is segment-aware, so/neuralis-othernever matches a/neuralisroot. Use it anywhere a user expects a path they can paste into their own terminal or editor: sync logs, tool results, file trees.resolveContainerPath(stack, hostPath)— the reverse. Models frequently paste back the host path they saw in a tool result; the shell policy gate and working-directory resolution translate it to the container form before spawn and policy mapping.
realProjectRoot(stack, projectId) returns both views
({ containerPath, hostPath }) for a project's filesystem root, and refuses
unsafe or sentinel project ids.
What the prompt block carries
The <runtime_stack> block an agent sees is rendered once per stream from
this snapshot plus two per-caller tables, in a fixed order: the host flags and
path roots; the source table (one entry per source the caller can address
— connector, on-disk reflection, effective read/write/exec policy,
description); the data_areas rows — one per visible package that
declares a manifest dataLayout,
naming its data:// directories (data://<zone>/{a,b} and, for per-agent
folders, data://<zone>/<agentId>/{…} under the stream's own agent id); the
residual scheme reference; the mount table; and the backing-service URLs.
A layout's machineWritten list is never part of that row. Everything in it
derives from boot-time facts and manifest declarations, so
it stays byte-stable for the whole conversation and the prompt cache holds.
Volatile per-source state (on/off, index counts) is rendered separately in
the trailing <workspace_live> reminder.
The mount table is a display convention
The mount_table lines rendered in the system prompt's runtime-stack block
(<key> → <container path> [ro] (host: …)) are labels for discovery — there
is no os connector and no tool resolves a mount label as a URI. To make a
host path agent-callable, attach it as a local-connector source with a real
URI prefix. The real, load-bearing data is RuntimeStackInfo.osMounts.
The file coordinate quadruple
Every package file (skill, rule, instruction, agent, doc) carries up to four
coordinates that tie the contribution to the filesystem — the
(uri, connector, osUri, containerDir) quadruple on PackageFile:
| Field | Meaning | When filled |
|---|---|---|
uri | Address in the filesystem-tool layer (packages://<dir>/... — the directory name, not the registry id — data://<src>/..., brain://...) | Project packages and source-contributed files; undefined for builtins read from the image |
connector | Connector kind serving the URI ('local', 'webtop', 'brain', …) | Always reflects the disk layout, even when uri is undefined |
osUri | Absolute path in the connector's own environment — host-side for host-anchored kinds, container-internal for sandbox kinds — produced by the connector's resolveOsUri. Read it together with connector to know which. | When the connector can derive one; UI and vectors consume it |
containerDir | Container-internal absolute path (for a skill: its bundle directory) | Always for builtin and project-package files; for source-fed files only when a local mount maps into the container |
A connector never fabricates a host path. One that cannot derive a real one
either throws a structured OsUriUnresolvableError — so URIs surfaced to
the UI and the vector store never contain stale fallbacks — or, when its store
is a sandbox whose internal absolute paths are themselves the true coordinate,
returns that internal path instead. Container-backed desktop sources take the
second route, which is why the host/container translators re-map only
host-anchored kinds. See
Connectors for the contract.
Activation substitution tokens
When a skill is activated through the execute tool, the host renders the
skill body — and resolves the skill's environment — with these tokens:
| Token | Resolves from | Typical use |
|---|---|---|
${SKILL_DIR} | The skill bundle's containerDir | bash ${SKILL_DIR}/scripts/run.sh; also expands inside the shell cwd parameter |
${SKILL_URI} | The skill's uri | fs_read access to bundle files when no container path exists |
${SKILL_CONNECTOR} | The skill's connector kind | Branching a body on how the bundle is reachable |
${SKILL_NAME} | The skill's id | Self-reference in logs and output |
${SESSION_ID} | The active stream session | Correlating script output with the conversation |
${NEURALIS_API} | services.apiBaseUrl | curl-based scripts calling host routes (authenticated by the session ticket) |
${SKILL_CREDENTIALS_RESOLVED} | The declared credentials: ids that resolved, comma-joined | Letting a body state which keys its scripts will find in the environment |
${SKILL_CREDENTIALS_MISSING} | The declared ids that did not resolve | Telling the model to degrade instead of failing on a missing key |
A ninth form, ${MISSING:<id>}, is per-credential rather than a fixed token: it
renders as (missing: <id>) or (present: <id>) for the id named inside it.
Values are never substituted — the tokens carry names and coordinates, and the
secrets themselves reach only the script's environment.
Substitution is a plain string pass, so an unknown ${...} placeholder is left
intact rather than blanked — it stays visible to the model and easy to spot.
When a skill's connector provides no container directory, ${SKILL_DIR}
resolves to an explanatory sentinel string and the platform shell cannot run its
scripts. When the skill's source can execute commands itself, the activation
envelope prints the skill's directory URI to pass as the execute working
directory; otherwise the skill body can still be read over ${SKILL_URI}. The full activation flow is
described in Skills.
How packages receive the snapshot
The host discovers the stack once at bootstrap and threads it to every
package through the bootstrap-complete lifecycle context, alongside the
aggregated URI policies (see
Lifecycle). Agents receive the same data as
the <runtime_stack> system-prompt block, generated from the snapshot —
never hand-edited per agent. Configuration values outside the documented
services block do not belong in the snapshot: anything secret goes through
the credential store (see
Credentials), never through environment
variables read ad hoc.