# Runtime configuration

NetScript splits configuration into two layers: a **typed project config** you author once in `netscript.config.ts` and load at startup (`@netscript/config`), and a **hot-reloadable runtime override layer** that operators roll out without redeploying (`@netscript/runtime-config`). Together they give you one validated, type-safe view of how the app is wired — defaults baked into the schema, values pulled from a config file and the environment, and last-mile operational overrides applied at runtime.

![Configuration resolution chain: schema defaults flow into the loaded config file, environment variables override typed values via resolveEnv, and hot-reloadable runtime override files apply last to produce a frozen resolved config.](https://rickylabs.github.io/netscript/netscript/assets/diagrams/runtime-config-resolution.svg)

*Resolution chain — schema defaults → config file (defineConfig / loadConfig) → environment (resolveEnv / getEnv) → runtime override files (loadRuntimeConfig) → the validated config the framework reads.*

> Use this when
>
> Reach for
>
> project config
>
> (
>
> @netscript/config
>
> ) when you need to declare
>
> how the app is built and wired
>
> — services, ports, databases, plugins, paths — once, with full TypeScript inference and Zod validation. Reach for
>
> runtime overrides
>
> (
>
> @netscript/runtime-config
>
> ) when an
>
> operator
>
> needs to toggle a feature flag, disable a job/saga/trigger, or add a task
>
> without a redeploy
>
> . For one-off typed reads of a single environment variable, use
>
> `getEnv`
>
> . See also
>
> architecture
>
> .

## What it is

Project config is the **static, schema-validated topology** of a NetScript app. You author it with `defineConfig` (or `defineConfigAsync` when the shape depends on mode/command), NetScript validates it against a Zod schema, and `loadConfig` / `initConfig` resolve the authored file into a fully typed `NetScriptConfig`. Environment variables feed into this layer through `resolveEnv` / `getEnv`, which coerce and default typed values per call.

Runtime config is a **separate, dynamic override layer**. It lives under a `runtime/` directory of versioned JSON files (jobs, sagas, triggers, features, tasks) selected by a `current` version pointer. `loadRuntimeConfig` reads it into a `RuntimeConfig` snapshot; `watchRuntimeConfig` reloads on change. Missing files produce **empty defaults** so startup never blocks on overrides that have not been rolled out yet. The two packages are deliberately distinct: `@netscript/config` owns the build-time contract, and `@netscript/runtime-config` owns the hot-reloadable operational layer.

## Learn → / Do →

[Learn — Architecture  How project config, the workspace layout, and the plugin system fit together at startup.](https://rickylabs.github.io/netscript/netscript/explanation/architecture/)

[Do — Choose a queue provider  A config-driven decision recipe: pick and wire a queue backend through project config.](https://rickylabs.github.io/netscript/netscript/data-persistence/how-to/choose-a-queue-provider/)

## Minimal example

Author the project config once, then load and inspect it at startup. The `runtimeConfig` section of the project config controls where the **generated** runtime schema/output lives — `netscript generate runtime-schemas` derives those schemas from registered plugin metadata (see [CLI & scaffold](https://rickylabs.github.io/netscript/orchestration-runtime/cli-scaffold/)); the live overrides are read separately by `@netscript/runtime-config`.

```ts
// netscript.config.ts
import { defineConfig } from '@netscript/config';

export default defineConfig({
  name: 'orders',
  version: '1.0.0',
  databases: {
    active: 'postgres',
    config: [{ provider: 'postgres', schema: 'database/postgres/schema' }],
  },
  services: {
    api: { port: 3000 },
  },
  plugins: ['@netscript/plugin-workers'],
});
```

```ts
// src/bootstrap.ts — load project config + apply runtime overrides
import { initConfig, getConfig } from '@netscript/config';
import { isFeatureEnabled, loadRuntimeConfig } from '@netscript/runtime-config';

// 1. Project config: validated once, then read synchronously anywhere via getConfig().
await initConfig();
const config = getConfig();

// 2. Runtime overrides: hot-reloadable JSON the operator controls (empty if unset).
const runtime = await loadRuntimeConfig();
const rolloutOn = isFeatureEnabled(runtime, 'worker-rollout', false);

console.log(config.name, config.services?.api?.port, rolloutOn);
```

## Key types first

The project config you author is `NetScriptConfigInput`; what `loadConfig` / `initConfig` return is the fully validated `NetScriptConfig`. These are the load-bearing fields.

**NetScriptConfig — the validated project config (returned by loadConfig / initConfig / getConfig)**

| Name | Type | Description |
| --- | --- | --- |
| `name` | `string (required)` | Project name. |
| `version` | `string (required)` | Project version. |
| `paths` | `PathsConfig (required)` | Workspace path conventions (services, packages, apps, workers, sagas, triggers, plugins, contracts, database, tasks, deploy). |
| `databases` | `DatabasesConfig (required)` | { active?: provider, config: DatabaseConfig[] } — configured databases and the active selector. |
| `plugins` | `string[] (required)` | Enabled plugin package names or specifiers. |
| `services` | `Record?` | Service configuration by service name. |
| `apps` | `Record?` | Frontend application configuration by app name. |
| `logging` | `LoggingConfig?` | { level, format, timestamps, colors? } logging behavior. |
| `aspire` | `AspireConfig?` | { appHost, dashboardPort } Aspire orchestration settings. |
| `sagas / triggers / gateway / sdk / deploy` | `section objects?` | Optional per-capability config sections. |
| `runtimeConfig` | `RuntimeConfigSection?` | Runtime schema/config OUTPUT settings (where generated runtime schema is written) — not the live overrides. |

The runtime override snapshot is a separate type — `RuntimeConfig` — returned by `loadRuntimeConfig()`. Each field is an array loaded from one topic directory.

**RuntimeConfig — the hot-reloadable override snapshot (returned by loadRuntimeConfig)**

| Name | Type | Description |
| --- | --- | --- |
| `jobs` | `JobOverride[]` | Job overrides from runtime/jobs/*.json (enabled, schedule, timeout, maxRetries, timezone, concurrency). |
| `sagas` | `SagaOverride[]` | Saga overrides from runtime/sagas/*.json (enabled, timeout, maxRetries, compensationTimeout). |
| `triggers` | `TriggerOverride[]` | Trigger overrides from runtime/triggers/*.json (enabled, paths). |
| `features` | `FeatureFlag[]` | Feature flags from runtime/features/*.json (id, enabled, description?, rolloutPercentage?). |
| `tasks` | `RuntimeTask[]` | Runtime task definitions from runtime/tasks/*.json (id, name, runtime, entrypoint, enabled?, timeout?, schedule?). |

## Resolution & precedence

There is no single monolithic merge function — resolution happens across the two packages, each with its own precedence. The order below is the conceptual chain the diagram depicts.

**How a value is resolved (lowest precedence first)**

| Name | Type | Description |
| --- | --- | --- |
| `1. Schema defaults` | `@netscript/config` | Zod schema fills omitted fields when defineConfig validates the authored input into NetScriptConfig. |
| `2. Config file` | `loadConfig / initConfig` | File search order: options.configFile → netscript.config.ts → .js → .mjs. cwd resolves from options.cwd → NETSCRIPT_PROJECT_ROOT → Deno.cwd(). |
| `3. Environment` | `resolveEnv / getEnv` | Per-variable: read the env var (def.env overrides the name) and coerce by type (string\|number\|boolean\|json); if absent, use default; if required and absent with no default, throw. |
| `4. Runtime overrides` | `loadRuntimeConfig` | Hot-reloadable JSON under the runtime dir, keyed by the 'current' version pointer; applied per-id (job/saga/trigger/feature/task). Missing files → empty defaults. |

**@netscript/config — primary functions**

| Name | Type | Description |
| --- | --- | --- |
| `defineConfig(input)` | `(NetScriptConfigInput) => NetScriptConfig` | Type-safe, validated config definition for netscript.config.ts. The 80% path. |
| `defineConfigAsync(fn)` | `((env: ConfigEnv) => …) => () => Promise` | Async/env-aware config when the shape depends on { mode, command }. |
| `loadConfig(options?)` | `(LoadConfigOptions?) => Promise` | Find, import, and validate the config file. Options: { cwd?, configFile? }. |
| `initConfig(options?)` | `(LoadConfigOptions?) => Promise` | Load once and cache; call at startup. Then read synchronously via getConfig(). |
| `getConfig()` | `() => NetScriptConfig` | Synchronous access to the cached config; throws if initConfig() has not run. |
| `isConfigLoaded() / clearConfigCache()` | `() => boolean / () => void` | Inspect or reset the config cache (clearConfigCache is for tests/reload). |
| `resolveEnv(schema) / getEnv(name, opts?)` | `typed env readers` | Coerce env vars (string\|number\|boolean\|json) with defaults; getEnv reads one variable. |
| `getMode() / isDev() / isProd() / isTest() / hasEnv(name)` | `env helpers` | Current mode (DENO_ENV/NODE_ENV) and presence checks. |
| `discoverWorkspace(root?) / findWorkspaceRoot(dir) / findMember(ws, name)` | `workspace` | Classify and locate Deno workspace members. |
| `inspectConfig(target)` | `(Partial \| string) => InspectionReport` | JSON-stable diagnostic report for CLI rendering. |

**@netscript/runtime-config — primary functions**

| Name | Type | Description |
| --- | --- | --- |
| `loadRuntimeConfig()` | `() => Promise` | Load overrides from the runtime dir via the 'current' pointer; empty defaults when files are missing. |
| `watchRuntimeConfig(onChange, opts?)` | `(cb, { signal?, prefix? }) => void` | Watch the runtime config files and invoke onChange after debounced reloads. |
| `isFeatureEnabled(config, flagId, defaultValue)` | `(RuntimeConfig, string, boolean) => boolean` | Read a feature flag, falling back when the flag is absent. |
| `getJobOverride / getSagaOverride / getTriggerOverride` | `(RuntimeConfig, id) => …Override \| undefined` | Look up a single override by id. |
| `getRuntimeTask(config, taskId)` | `(RuntimeConfig, string) => RuntimeTask \| undefined` | Get a runtime task definition by id. |
| `summarizeRuntimeConfig(config, prefix?)` | `(RuntimeConfig, string?) => RuntimeConfigSummary` | Structured summary of active overrides (disabled jobs/sagas/triggers/features, message lines) for caller-owned presentation. |
| `RUNTIME_CONFIG_TOPICS` | `('jobs'\|'sagas'\|'triggers'\|'features'\|'tasks')[]` | The topic names backed by versioned JSON files. |

## Runtime override layout

The runtime override directory is resolved from the environment, then read through the `current` pointer that names the active version of each topic file.

```ts
// src/feature-gate.ts
import { isFeatureEnabled, loadRuntimeConfig } from '@netscript/runtime-config';

const runtime = await loadRuntimeConfig();

// Third arg is the fallback when the flag file/entry is absent.
if (isFeatureEnabled(runtime, 'worker-rollout', false)) {
  // run the gated path
}
```

```ts
// src/runtime-watch.ts
import { summarizeRuntimeConfig, watchRuntimeConfig } from '@netscript/runtime-config';

const controller = new AbortController();

watchRuntimeConfig(async (config) => {
  const summary = summarizeRuntimeConfig(config);
  // re-apply overrides; summary.messages is presentation-ready
  console.log(summary.messages.join('\n'));
}, { signal: controller.signal });
```

```ts
// src/env.ts
import { getEnv, resolveEnv } from '@netscript/config';

const port = getEnv('PORT', { type: 'number', default: 3000 });

const env = resolveEnv({
  DATABASE_URL: { env: 'DB_URL', required: true },
  DEBUG: { type: 'boolean', default: false },
});
```

## Production notes

> Two packages, two precedence rules — not one merge
>
> There is
>
> no single function
>
> that merges defaults, file, env, and runtime overrides into one object.
>
> @netscript/config
>
> validates and loads the
>
> project
>
> config; per-variable env coercion is explicit via
>
> resolveEnv
>
> /
>
> getEnv
>
> ; and
>
> @netscript/runtime-config
>
> is a
>
> separate
>
> hot-reloadable layer your code reads with
>
> loadRuntimeConfig
>
> and applies per id. Don't expect a runtime override file to silently change your
>
> NetScriptConfig
>
> — you read and apply the override explicitly.

> Runtime dir resolution & missing files
>
> The runtime override directory is resolved as
>
> NETSCRIPT_RUNTIME_CONFIG_DIR
>
> → else the parent of
>
> NETSCRIPT_TASKS_DIR
>
> → else
>
> ./runtime
>
> . A missing directory, pointer, or topic file is
>
> not an error
>
> —
>
> loadRuntimeConfig
>
> returns empty defaults so startup proceeds while operators roll overrides out independently. Make sure your deployment sets the env var (or the working directory) you expect, or you may silently read empty overrides.

> getConfig() must follow initConfig()
>
> getConfig()
>
> returns the cached config and
>
> throws
>
> if
>
> initConfig()
>
> has not run. Call
>
> initConfig()
>
> once at startup, then read synchronously anywhere. In tests, use
>
> clearConfigCache()
>
> to force a reload between cases.

## Reference →

This hub is intentionally thin — the full generated API surface for both packages lives in the reference.

[runtime-config](https://rickylabs.github.io/netscript/netscript/reference/runtime-config/)

[Look up — @netscript/runtime-config  loadRuntimeConfig, watchRuntimeConfig, the override types, and the topic helpers.](https://rickylabs.github.io/netscript/netscript/reference/runtime-config/)

[Look up — @netscript/config  defineConfig, loadConfig/initConfig, resolveEnv/getEnv, the NetScriptConfig schema, and workspace helpers.](https://rickylabs.github.io/netscript/netscript/reference/config/)

[Understand — Architecture  Where project config, the workspace, and the plugin system sit in the framework.](https://rickylabs.github.io/netscript/netscript/explanation/architecture/)

[Browse — Capabilities  All NetScript capabilities: services, jobs, sagas, triggers, database, and more.](https://rickylabs.github.io/netscript/netscript/capabilities/)

[Polyglot tasks](https://rickylabs.github.io/netscript/netscript/background-processing/polyglot-tasks/) [Capabilities](https://rickylabs.github.io/netscript/netscript/capabilities/)
