Ashlar configures via environment variables and optional ~/.ashlar/config.json. This document lists the primary configuration options; see inline code comments for the full set. Read the next section before reaching for appsettings.json.
There are three kinds of Ashlar:* option, and they do not read the same configuration:
- Kernel options — environment variables ONLY.
AddAshlar()(src/Ashlar.Hosting/AshlarServiceCollectionExtensions.cs,AshlarServiceCollectionExtensions.AddAshlar) builds its ownIConfigurationfromAddEnvironmentVariables()alone and hands that to every module it composes. In Ashlar.API and Ashlar.CLI the options those modules bind therefore come fromAshlar__Section__Keyenvironment variables, andappsettings.jsondoes not reach them — a"Ashlar": { "Meai": { ... } }block in the API'sappsettings.jsonis silently ignored. This covers at leastAshlar:Meai:*/Ashlar:UseMeaiPipeline,Ashlar:NodeCapabilityRuntime:*,Ashlar:RemoteCapabilities:*,Ashlar:RunPod:*,Ashlar:WorkloadScaling:*andAshlar:MeshLab:WorkerExecutor:*. Set them asAshlar__Meai__OllamaBaseUrl=...etc. (the compose stacks do exactly this). Documented as a known v0 limitation; the fix is architectural, not a docs fix. - Host-owned options — the host's configuration (
appsettings.json+ environment). Sections the API binds itself frombuilder.Configurationinapplication/src/Ashlar.API/Program.cs—Ashlar:Security:*,Ashlar:Execution:*,Ashlar:Barriers:*,Ashlar:Routing:*,Ashlar:Mcp:*,Ashlar:A2A:*,Ashlar:GrpcTransport,Ashlar:Product,Ashlar:Entitlements,Ashlar:PrivateLicense,Ashlar:PatternStorePath— readappsettings.jsonandAshlar__*variables alike, with the usual precedence (environment wins). - Host-composed options — whatever configuration you pass.
Ashlar:Autonomy:*is bound byAddAshlarAutonomy(configuration), which the shipped hosts do not call; a host that composes the loop decides what it passes.
The tables below list keys in Ashlar:A:B form with the Ashlar__A__B environment spelling beside them; whether a JSON file can supply the key at all is decided by which of the three groups above the section belongs to, not by the form the table happens to use.
ASHLAR_EGRESS_MODE is read once by AddAshlar, or at the first process-bound decision when there is no host. Restart the process after changing it. It is not read from appsettings.json or other IConfiguration providers.
| Profile | Default | ASHLAR_EGRESS_MODE=report |
ASHLAR_EGRESS_MODE=enforce |
|---|---|---|---|
| AirGapped | enforce | ignored; Warning and override-ignored |
enforce |
| SecureWorkstation | enforce | break-glass; Warning and break-glass |
enforce |
| Full, Server, Edge, System | report | report | enforce |
Unknown values fail closed to enforcement and produce a Warning. AshlarHostingOptions.EgressMode can only raise enforcement. Once AirGapped has been resolved in a process it cannot be lowered; SecureWorkstation can only be replaced by AirGapped. The variable is the only break-glass Ashlar latches and announces: a host that registers its own IEgressGuard decides its HTTP and MEAI routes on SecureWorkstation with that guard's profile and mode, with no latch and no Warning (a host guard built as new EgressGuard("secure-workstation", "report") even records basis break-glass), while AirGapped's process floor holds even under a host guard (see the egress inventory, "AirGapped route floor").
A host may opt its own named factory client into report mode, including after AddAshlar:
services.Configure<EgressGuardOptions>(options => options.ReportOnlyClients.Add("host-client"));This uses Ashlar.Infrastructure.Egress.EgressGuardOptions. Each configured name logs a startup Warning at event 7308 EgressHostClientOptOut (distinct from the 7307 degrade Warnings) and decisions record host-opt-out. AirGapped ignores the exception. Naming an Ashlar-owned client, including the unnamed default client, fails host startup. The exception applies only to that factory client and its redirects; faults still fail closed.
Enforced refusals stop before the remote send and log an operator decision at Warning. The local refusal contains a reason, site and random reference; a refusal in export, mesh export or pkg export/publish/share exits 77. Other verbs exit with their own codes: an EG-PROC-09 child-process refusal that reaches no handler exits 1, validate reports a refused test process as a failed validation (2), and commands whose catch-all returns UnexpectedError exit 10 (a known limit). Without a subject frame, data is SystemHigh, so only Host destinations are allowed. Named operator file exports retain their recorded operator-verb exception; publish/share remain enforced. See the egress inventory for routes and known limits, including unmediated inbound responses and Host relays.
SPEC-007 PR 5's first slice (#748) adds a clearances block to the project's ashlar.policy.yaml: the ceiling on what each agent the project composes may read.
clearances:
default: Internal # an agent that agents: does not name
agents:
analyst: Secret//C:ALPHA # keyed by the agent name, compared ordinally- Each value is canonical security-label text: a level (
Public,Internal,Confidential,Secret,TopSecret) with optional//C:compartments and//K:caveats, orSystemHigh(SPEC-007 §2.4). Anything else, a lowercase level included, rejects the whole policy when it loads (PolicyLoader.TryLoad), and so do an empty value and a blank name underagents. //does not start a YAML comment (a comment starts with#after whitespace), so these values need no quotes; quoted values work too.- With no
clearancesblock, or no entry for the agent and nodefault, the agent's clearance isPublic. AshlarHostingOptions.Clearanceis an optional host ceiling in the same text form. The effective clearance is the meet of the two, so a host can only tighten the policy. Null or empty leaves the policy clearance; any other value that does not parse fails closed toPublic. Ashlar never binds it fromIConfiguration.- A sealed skill declares its highest level on its package (
HighestLevel), covered by the package seal and not by the signed gate record; a missing or unparseable level isSystemHigh.SkillComposition.Deciderefuses a skill whose level the agent's clearance does not dominate. - Nothing consumes this in production yet.
SkillComposition.Decideis a library API with no production caller, and no shipped host readsclearancesorAshlarHostingOptions.Clearancewhen it composes an agent. A valid clearance therefore changes no running behaviour; an invalid one still rejects the policy. Responses on inbound connections are not mediated.
| Variable | Description | Default |
|---|---|---|
ASHLAR_CONFIG_PATH |
Path to config file | ~/.ashlar/config.json |
ASHLAR_STATE_DIR |
Runtime-state directory for LiteDB stores and snapshots (see "Runtime state" below); absolute, or relative to the resolved repo/app root | <repo or app root>/.ashlar/state |
ASHLAR_DOCS_ROOT |
Root that ashlar improve writes promoted-adaptation documentation (docs/bricks/<brick>.md) beneath; absolute, or relative to the resolved repo/app root. The CLI E2E tests point it at their temp directory so a promotion never lands in the checkout |
<repo or app root> (Ashlar documents itself in its own tree) |
ASHLAR_MESH_INSTANCES_PATH |
Path to instances.json for ashlar mesh discovery |
~/.ashlar/instances.json |
ASHLAR_MESH_TRUST_POLICY |
Peer trust policy for ashlar mesh discovery and capability requests: any, allowlist (only admitted: true peers), trusted-only, trusted-preferred; any other value normalizes to trusted-preferred (fail-closed). Falls back to ASHLAR_PEER_TRUST_POLICY when unset (MeshTrustPolicyConfiguration) |
unset → any for discovery, trusted-preferred for capability requests |
ASHLAR_MESH_DIRECTOR_BASE_URL |
Base URL for commercial mesh director CLI (dotnet run --project commercial/src/Ashlar.Commercial.MeshDirector -- director ...) HTTP calls |
unset |
ASHLAR_MESH_API_KEY |
Optional X-Ashlar-Api-Key for director CLI |
unset |
ASHLAR_MESH_MUTATING_TOKEN |
Optional X-Ashlar-Mesh-Token for mutating mesh routes on the hub |
unset |
ASHLAR_MESH_PEER_REGISTRATION_KEY |
Per-peer fleet registration secret for commercial mesh director CLI register (when director requires distinct key) |
unset |
ASHLAR_DEPLOYMENT_PROFILE |
Hosting dependency profile for AddAshlar() module composition. Canonical values: full, server, edge, air-gapped, secure-workstation (alias workstation), system. Hyphens, underscores, and case fold (airgapped, air_gapped, secure_workstation). air-gapped is the slim offline profile (no trust/agents/observation). secure-workstation is the IDE daemon profile — not a synonym for air-gapped. The profile registers modules; trust still requires ASHLAR_TRUST_ENABLED=1 or TrustEnabled = true. |
full |
ASHLAR_STRICT_MODE |
1 or true = enable strict mode (fail-fast + verbose diagnostics for dev/CI; disable for production) |
false |
ASHLAR_AIRGAP |
1 or true = air-gapped; no cloud calls |
unset |
ASHLAR_AIRGAP_PROBE |
1 = probe network to detect air-gap |
unset |
ASHLAR_TRUST_ENABLED |
1 = enable Trust & sanitization |
false |
ASHLAR_MODEL_PROVIDER |
Default LLM provider | from config |
ASHLAR_LOOP_PARALLEL |
1 = parallel loop kernel |
false |
ASHLAR_LOOP_INSTRUMENT |
1 = instrumented loop |
false |
ASHLAR_LLM_RETRY_COUNT |
Retries for cloud LLM (5xx/429) | 3 |
Strict mode is designed for development and CI environments. When enabled, Ashlar fails fast and emits verbose diagnostics instead of silently falling back to defaults or retrying on errors. Flip it to permissive (disabled) once confident in the agentic layer for production.
Master switch: ASHLAR_STRICT_MODE=1 enables all sub-flags below. Individual flags can override the master switch.
| Variable / Config Key | Description | Default |
|---|---|---|
ASHLAR_STRICT_MODE |
Master switch — enables all sub-flags | false |
Ashlar:StrictMode:FailFastOnValidationErrors |
Throw immediately on validation failures | follows master |
Ashlar:StrictMode:FailFastOnProviderErrors |
Throw on provider misconfiguration instead of fallback | follows master |
Ashlar:StrictMode:FailFastOnPipelineErrors |
Throw on pipeline stage errors instead of retrying | follows master |
Ashlar:StrictMode:VerboseDiagnostics |
Emit debug-level logging and detailed error messages | follows master |
Ashlar:StrictMode:FailOnConfigurationWarnings |
Treat missing config files / empty configs as hard errors | follows master |
Usage examples:
# Development / CI — fail fast and verbose
export ASHLAR_STRICT_MODE=1
# Production — permissive (default)
# unset ASHLAR_STRICT_MODE (or ASHLAR_STRICT_MODE=0)
# Fine-grained: strict for providers only
export ASHLAR_STRICT_MODE=0
# then, in your host's composition (the sub-flags are set programmatically; the shipped hosts
# do not bind Ashlar:StrictMode from appsettings.json — see "How Ashlar:* options bind" above):
# services.AddAshlar(o => o.StrictMode.FailFastOnProviderErrors = true);LiteDB stores and rollback snapshots that Ashlar writes at runtime — ashlar-patterns.db, ashlar-adaptation.db, ashlar-adaptation-audit.db, ashlar-copilot-tasks.db, ashlar-execution.db, ashlar-test-failures.db, ashlar-snapshots/ — resolve through RepoPathResolver.ResolveStateDirectory:
Ashlar:PatternStorePath(API / daemon) or--store-path(CLI) when set: everything is co-located with that file / directory.- Otherwise
ASHLAR_STATE_DIRwhen set — absolute, or relative to the resolved repo/app root. - Otherwise
<repo or app root>/.ashlar/state/(the root is the nearest directory containingAshlar.sln, else the current directory)..ashlar/is gitignored; the directory is created on first use.
Backward compatibility: an install that already has ashlar-*.db files directly at the repo root and no .ashlar/state/ yet keeps using the root (nothing is moved or logged). To migrate, stop Ashlar and move the ashlar-*.db files and ashlar-snapshots/ into .ashlar/state/.
Containers: the API and CLI images set ASHLAR_STATE_DIR=/data/state (owned by the non-root app user); the portal and agent-server compose stacks mount the ashlar-state named volume there (docs/DEPLOYMENT.md, "Runtime state").
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Api__EnableSwagger |
Serve /swagger (UI) and /swagger/v1/swagger.json. The document enumerates every mapped route and schema, so it is off outside Development unless set. The commercial Fleet.Host honours the same key with the same default. application/src/Ashlar.API/appsettings.Testing.json sets it to true and the API, quickstart and fleet-host images all ship that file, so ASPNETCORE_ENVIRONMENT=Testing serves Swagger too (docs/DEPLOYMENT.md) |
true in Development, else false |
GET /health (liveness, constant 200) and GET /ready (readiness: 503 while the host is starting or shutting down, 200 in between) are always mapped, unauthenticated, and outside /api.
All tunable constants are centralized in Ashlar.Core.Domain.AshlarDefaults. This eliminates hard-coded magic numbers scattered across the codebase. Override any value via environment variables or appsettings.json — keys are listed in the relevant sections below.
The profile does not enforce firewalls or Tailscale ACLs, but it fails closed: Lan, Tailnet and Public refuse to start while AuthorizationMode is None (and the legacy flag is off) unless AllowUnauthenticatedNetworkExposure=true is set explicitly. See SECURITY.md ("Default posture and in-scope surfaces"), docs/TailscaleAndAshlar.md and docs/config/security-exposure.env.example.
| Variable | Description | Default |
|---|---|---|
Ashlar__Security__ExposureProfile |
Localhost, Lan, Tailnet, or Public (case-insensitive). Off-loopback values require built-in auth (see above) |
Localhost in appsettings.json |
Ashlar__Security__AllowUnauthenticatedNetworkExposure |
true — escape hatch: start with an off-loopback profile and no built-in auth (logs a warning). Only when an authenticating proxy or network ACL fronts the API |
false |
Ashlar__Security__CustomAdvisory |
Optional extra line shown in the Director portal advisory | unset |
Ashlar__Security__ShowAdvisoryInPortal |
true / false — show advisory banner in portal |
true |
Ashlar__Security__RequireApiKeyForMutatingEndpoints |
true / false — legacy: enforce API key checks for POST/PUT/PATCH/DELETE under /api/*. Fails closed: with no ApiKey configured, mutating requests get 401 |
false |
Ashlar__Security__ApiKey |
Shared secret required for protected mutating requests | unset (disabled) |
Ashlar__Security__ApiKeyHeaderName |
Header used for key checks | X-Ashlar-Api-Key |
Ashlar__Security__ExcludedApiKeyPaths |
Comma-separated API path prefixes exempted from key checks | none |
Ashlar__Security__AuthorizationMode |
Built-in auth mode: None, ApiKey, BearerToken, Basic, ApiKeyOrBearerToken, ApiKeyOrBasic, BearerTokenOrBasic, Any |
None |
Ashlar__Security__AuthorizationScope |
Built-in auth scope: MutatingApi or AllApi |
MutatingApi |
Ashlar__Security__RequireAuthForCopilotReadApis |
true / false — credential GET /api/copilot/tasks* even under MutatingApi, because task history carries the prompts and outputs of past runs. Set false only if that history is not sensitive to you |
true |
Ashlar__Security__ExcludedAuthorizationPaths |
Comma-separated API path prefixes exempted from built-in auth checks | none |
Ashlar__Security__BearerToken |
Shared secret for bearer token authorization | unset |
Ashlar__Security__BearerTokenHeaderName |
Header used for bearer token checks | Authorization |
Ashlar__Security__BearerTokenScheme |
Bearer scheme prefix when using Authorization header |
Bearer |
Ashlar__Security__BasicAuthUsername |
Username for built-in basic auth | unset |
Ashlar__Security__BasicAuthPassword |
Password for built-in basic auth | unset |
Ashlar__Security__BasicAuthHeaderName |
Header used for basic auth checks | Authorization |
Notes:
- If
AuthorizationModeis set to anything exceptNone, built-in auth mode takes precedence over legacyRequireApiKeyForMutatingEndpoints. RequireApiKeyForMutatingEndpointsremains for backward compatibility with existing deployments. Like every other mode it fails closed when its credential is missing.Ashlar__Security__ApiKey/BearerToken/BasicAuthPasswordare compared in constant time against the configured plaintext value; they are not hashed at rest. Keep them in environment / secret stores, not in committedappsettings.json.
Shipped hosts (Ashlar.API, ashlar background-agent daemon) log human-readable console lines and keep metrics in an in-process MemoryMetricsCollector by default. Structured output and export are opt-in and read through the host configuration (builder.Configuration in Program.cs), so appsettings.json, environment variables and UseSetting all work for these keys, unlike the env-only kernel Ashlar:* options.
| Variable / config key | Description | Default |
|---|---|---|
ASHLAR_LOG_JSON (or Ashlar:Logging:Json / Ashlar__Logging__Json) |
1 / true switches the console logger to AddJsonConsole (one JSON object per line: Timestamp (UTC, ISO-8601), EventId, LogLevel, Category, Message, State, scopes). Applies to Ashlar.API and the CLI daemon; other CLI commands keep plain console output |
off (plain console) |
OTEL_EXPORTER_OTLP_ENDPOINT |
Standard OTel variable. When set, Ashlar.API calls AddAshlarOpenTelemetry and exports traces (ASP.NET Core + HttpClient instrumentation) and metrics (the Ashlar meter plus ASP.NET Core / HttpClient) over OTLP; IMetricsCollector becomes OpenTelemetryMetricsCollector. An unreachable collector never fails startup — the exporter batches in the background and logs failures via OTel self-diagnostics |
unset (no export, in-process MemoryMetricsCollector) |
OTEL_EXPORTER_OTLP_PROTOCOL, OTEL_EXPORTER_OTLP_HEADERS, OTEL_EXPORTER_OTLP_TIMEOUT, OTEL_SERVICE_NAME, OTEL_RESOURCE_ATTRIBUTES |
Honoured by the OpenTelemetry SDK as usual once export is on. OTEL_SERVICE_NAME defaults to Ashlar.API when unset |
SDK defaults (grpc, 10 s, Ashlar.API) |
Metric shape over OTLP: IMetricsCollector keys (for example ncr.model_load.success, ncr.ollama.chat.duration) are not individual OTLP instruments; they arrive as attribute values on two instruments from the Ashlar meter — ashlar.operation.duration (histogram, ms, attribute operation) and ashlar.operation.count (counter, attribute counter). See docs/NcrReleaseSLOs.md for how the SLO names map, and docs/DEPLOYMENT.md § Observability for compose usage.
POST /api/execution/build and POST /api/execution/run let a RemoteExecutionPlatform caller (ASHLAR_EXECUTION_REMOTE_URL) build images and run containers on this host's Docker daemon, including host bind mounts. They are not mapped unless opted in, and refuse AuthorizationMode=None (403) even when opted in.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Execution__ServeRemoteExecution |
true maps /api/execution/build and /api/execution/run. Requires a built-in Ashlar__Security__AuthorizationMode other than None; otherwise every request is refused with 403 |
false (routes return 404) |
Ashlar__Execution__AllowedVolumeMountRoot |
Single host directory under which VolumeMounts host paths on /api/execution/run are accepted (the remote caller mounts only its test-results directory). Unset = any request carrying volume mounts is rejected with 400 |
unset |
Optional middleware runs before built-in API auth. It applies to /api/mesh/* and POST /api/bricks/*/execute. When all options are unset or zero, behavior matches previous releases (no extra mesh checks).
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Security__Mesh__MeshMutatingToken |
When set, POST/PATCH/DELETE under /api/mesh must send this exact value in the mesh token header |
unset |
Ashlar__Security__Mesh__MeshTokenHeaderName |
Header for mesh mutating token | X-Ashlar-Mesh-Token |
Ashlar__Security__Mesh__BrickExecuteToken |
When set, brick execute requires this value in the brick header only | unset |
Ashlar__Security__Mesh__BrickExecuteTokenHeaderName |
Header for brick execute token | X-Ashlar-Brick-Execute-Token |
Ashlar__Security__Mesh__MaxJsonBodyBytes |
Reject POST/PUT/PATCH when Content-Length exceeds this (0 = off) |
524288 |
Ashlar__Security__Mesh__RateLimitPermitLimit |
Max mutating requests per client IP per window for mesh + brick execute (0 = off) | 120 |
Ashlar__Security__Mesh__RateLimitWindowSeconds |
Window length in seconds | 60 |
When BrickExecuteToken is unset but MeshMutatingToken is set, brick execute accepts the mesh secret in BrickExecuteTokenHeaderName or MeshTokenHeaderName.
Combine with Ashlar__Security__AuthorizationMode and TLS termination for production meshes. See docs/MeshPhase2TransportAndAuth.md.
For /api/mesh/* and POST /api/bricks/*/execute, the API assigns or echoes X-Ashlar-Correlation-Id (see MeshPhase3DistributedExecution.md). Clients may send their own correlation id to align logs across hops.
Only active when AddAshlar registers adaptation (Full/Server/AirGapped profiles with pattern store). Binds section Ashlar:Mesh:KnowledgeSync.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Mesh__KnowledgeSync__Enabled |
true to run periodic peer pull |
false |
Ashlar__Mesh__KnowledgeSync__PeerBaseUrls__0 |
First peer API base URL (https, trailing slash optional) | unset |
Ashlar__Mesh__KnowledgeSync__IntervalMinutes |
Minutes between pull rounds | 15 |
Ashlar__Mesh__KnowledgeSync__SinceLookbackMultiplier |
since = now - interval * multiplier for export window |
2 |
Ashlar__Mesh__KnowledgeSync__MaxAdaptations |
Cap per export GET | 500 |
Ashlar__Mesh__KnowledgeSync__MaxPatterns |
Cap per export GET | 500 |
See MeshPhase4KnowledgeSync.md.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Mesh__Elastic__Enabled |
true to run periodic re-placement for stale Pending tasks |
false |
Ashlar__Mesh__Elastic__IntervalMinutes |
Minutes between rebalancer rounds | 2 |
Ashlar__Mesh__Elastic__PendingStaleSeconds |
Pending tasks older than this get TryScheduleAsync again |
120 |
Workers should POST heartbeat with queueDepth (local backlog) so placement prefers idle nodes. See MeshPhase5ElasticScheduling.md.
Binds section Ashlar:Mesh:Checkpoint. See MeshPhase6LeasesAndCheckpoints.md.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Mesh__Checkpoint__LeaseSeconds |
Default lease duration after assignment (seconds) when schedule body omits leaseSeconds |
1800 |
Ashlar__Mesh__Checkpoint__SweepEnabled |
true to periodically move Assigned/Running tasks with expired leases to Pending |
false |
Ashlar__Mesh__Checkpoint__SweepIntervalMinutes |
Minutes between sweep passes | 1 |
Binds section Ashlar:Mesh:Persistence. See MeshPhase9DirectorPersistence.md.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar__Mesh__Persistence__Provider |
InMemory or LiteDb |
InMemory |
Ashlar__Mesh__Persistence__DatabasePath |
LiteDB file path when provider is LiteDb | mesh-director.db |
Used by commercial mesh director CLI (dotnet run --project commercial/src/Ashlar.Commercial.MeshDirector -- director ...) when a worker or script talks to a remote Ashlar.API mesh control plane. See docs/MeshPhase7EdgeAlignment.md. Discovery-related ASHLAR_MESH_* values also appear under Core above; this section summarizes hub HTTP access from the CLI.
| Variable | Description |
|---|---|
ASHLAR_MESH_DIRECTOR_BASE_URL |
Director base URL (e.g. https://hub:8080) |
ASHLAR_MESH_API_KEY |
Optional X-Ashlar-Api-Key when the hub enforces API key auth |
ASHLAR_MESH_MUTATING_TOKEN |
Optional X-Ashlar-Mesh-Token for mutating /api/mesh requests |
ASHLAR_MESH_PEER_REGISTRATION_KEY |
Per-peer registration secret for commercial mesh director CLI register |
Pipeline options resolve in this order: defaults, config (Ashlar:Pipelines:*), then environment variables.
| Variable | Description | Default |
|---|---|---|
ASHLAR_PIPELINE_MAX_RETRIES |
Max stage retry attempts before failure | 3 |
ASHLAR_PIPELINE_RETRY_DELAY_MS |
Delay between retries in milliseconds | 100 |
ASHLAR_PIPELINE_RESUME_FAILED |
1/true to resume failed stages by default |
false |
ASHLAR_PIPELINE_ALLOW_MISSING_RESUME_SOURCE |
1/true to continue when source run is missing |
false |
ASHLAR_PIPELINE_ENABLE_TEST_HOOKS |
Enables deterministic failure/test hooks for gate scenarios | false |
ASHLAR_PIPELINE_COMPLETION_POLICY |
Completion policy override (for example AllowNonCriticalStageFailures) |
Strict |
ASHLAR_PIPELINE_STORE_PROVIDER |
Pipeline run store provider (for example LiteDb) |
in-memory |
ASHLAR_PIPELINE_STORE_PATH |
Store path when using file-backed providers | unset |
ASHLAR_PIPELINE_DETERMINISTIC_ADAPTER |
Override deterministic adapter identifier | framework default |
ASHLAR_PIPELINE_AGENTIC_ADAPTER |
Override agentic adapter identifier | framework default |
| Variable | Description | Default |
|---|---|---|
OPENAI_API_KEY |
API key | required for openai provider |
OPENAI_MODEL |
Model name | gpt-4o-mini |
OPENAI_BASE_URL |
Chat completions URL or API root (https://api.openai.com, https://api.openai.com/v1, or full .../v1/chat/completions); normalized to POST .../v1/chat/completions |
https://api.openai.com/v1/chat/completions |
OPENAI_VISION_MODEL |
Vision model | OPENAI_MODEL |
Use openai_compat when the backend implements OpenAI-style POST /v1/chat/completions with Authorization: Bearer (vLLM, LiteLLM, llama.cpp server, etc.). Separate from openai so local gateways do not overwrite official OpenAI env vars.
| Variable | Description | Default |
|---|---|---|
OPENAI_COMPAT_API_KEY |
Bearer token (required for this provider; use a placeholder if the server ignores auth) | unset |
OPENAI_COMPAT_BASE_URL |
Origin (http://127.0.0.1:8000), API root (.../v1), or full completions URL; normalized like OPENAI_BASE_URL |
unset |
OPENAI_COMPAT_MODEL |
Text model id | default (see AshlarDefaults.OpenAiCompatDefaultModel) |
OPENAI_COMPAT_VISION_MODEL |
Vision model when OPENAI_COMPAT_MODEL is not suitable for image input |
OPENAI_COMPAT_MODEL |
Per-request config.model overrides the vision model when set.
| Variable | Description | Default |
|---|---|---|
AZURE_OPENAI_ENDPOINT |
Endpoint URL | required |
AZURE_OPENAI_API_KEY |
API key | required |
AZURE_OPENAI_DEPLOYMENT |
Deployment name | required |
AZURE_OPENAI_API_VERSION |
API version | 2024-06-01 |
Three code paths reach Ollama — the default MEAI model path, the NCR serving backend, and the legacy provider factory — and each has its own key family. They all resolve in the same order, so setting the legacy pair alone is enough on a single host; inside Compose set all three (the shipped stacks do) so no path can fall back to localhost:11434, which is the container itself.
When hosting a brick that uses only deterministic execution (no model inference), the host may log warnings during startup about failing to reach the Ollama backend:
warn: NCR startup health check degraded: failed to reach Ollama backend
These warnings are expected and non-blocking when your application does not use model features. The host performs availability probes for all configured providers during initialization, but the brick's ExecuteAsync will still run successfully. These warnings do not prevent host startup or brick execution.
To eliminate these warnings, either:
- Ensure Ollama is running and accessible at the configured
OLLAMA_BASE_URL/Ashlar:Meai:OllamaBaseUrl - Or accept them as informational when your brick is deterministic-only
Resolution order (first non-empty value wins): ASHLAR_OLLAMA_BASE_URL / ASHLAR_OLLAMA_MODEL env → the path's own config key (Ashlar:Meai:* or Ashlar:NodeCapabilityRuntime:Ollama:BaseUrl) → legacy OLLAMA_BASE_URL / OLLAMA_MODEL env → the path's default (http://localhost:11434 / llama3.1:latest; NCR uses http://127.0.0.1:11434). Blank values are treated as unset.
| Variable / Key | Path | Description | Default |
|---|---|---|---|
ASHLAR_USE_MEAI_PIPELINE (Ashlar:UseMeaiPipeline) |
selector | 0/false opts out of the default Microsoft.Extensions.AI (MEAI) model path and uses the legacy provider factory instead. Default: on. |
on |
ASHLAR_OLLAMA_BASE_URL |
MEAI + NCR | Highest-precedence Ollama base URL override. | unset |
ASHLAR_OLLAMA_MODEL |
MEAI | Highest-precedence Ollama model override. | unset |
Ashlar:Meai:OllamaBaseUrl (Ashlar__Meai__OllamaBaseUrl) |
MEAI | Base URL for the default IModel / IChatClient path (local:ollama target). |
falls through |
Ashlar:Meai:OllamaModel (Ashlar__Meai__OllamaModel) |
MEAI | Model for the default IModel / IChatClient path. |
falls through |
Ashlar:NodeCapabilityRuntime:Ollama:BaseUrl (Ashlar__NodeCapabilityRuntime__Ollama__BaseUrl) |
NCR | Base URL for the NCR serving backend, startup health probe and /api/tags model catalog (see below). |
falls through |
OLLAMA_BASE_URL |
all (legacy) | Base URL. The provider-factory path (provider=ollama, /api/onboarding/status, IDE endpoints) reads only this family; MEAI and NCR fall back to it. |
http://localhost:11434 |
OLLAMA_MODEL |
all (legacy) | Text model. | llama3.1:latest |
OLLAMA_VISION_MODEL |
provider factory | Vision model | richardyoung/smolvlm2-2.2b-instruct |
OLLAMA_TIMEOUT_SECONDS |
provider factory | Request timeout | 300 |
GET /api/onboarding/status reports meaiOllamaBaseUrl / meaiOllamaModel (what the default model path will dial) next to the provider-factory ollama availability, and scripts/prod-dry-run.sh fails when the former is a loopback address inside Compose.
Docker (models in containers): docker compose -f deploy/compose/docker-compose.ollama.yml up -d, then scripts/run-ollama-docker.ps1 / scripts/run-ollama-docker.sh to pull a tag. Host-run Ashlar.API with Ollama in Docker (all platforms): scripts/start-ashlar-api-dev.ps1 or scripts/start-ashlar-api-dev.sh (waits for Ollama, sets OLLAMA_* + NCR URL, runs dotnet run). Use -Pull / --pull when the model is not yet local. Phone / another device on the same LAN: -ListenLan / --listen-lan binds http://0.0.0.0:<port>; browse http://<host-LAN-IP>:8080 and allow the port in the host firewall. Default bind is loopback-only (127.0.0.1). Stop: scripts/stop-ashlar-api-dev.ps1 / .sh.
EnvironmentHardwareProfiler is the only IHardwareProfiler the shipped path registers
(NodeCapabilityRuntimeServiceCollectionExtensions), and it is reached from Ashlar.Hosting kernel
phase 01 whenever a deployment profile sets IncludeNodeCapabilityRuntime: true. RAM is measured
(GC.GetGCMemoryInfo). Everything else in the table below is read from the environment, because
there is no cross-platform way to read it — so on a node where nothing sets these, the profile
describes a machine with no GPU, idle CPU, mains power and empty disks.
Set
ASHLAR_TOTAL_VRAM_BYTESon any node with a GPU. It defaults to0,AvailableVRAMBytesfalls back to it, and bothLinuxPolicyandWindowsPolicyrequireMinVRAMRequiredBytes <= AvailableVRAMBytes. Two of the four models inDefaultModelSuite—llama3-8bandqwen-coder-7b— declareMinVRAMRequiredBytesof 6 GiB, so with VRAM unreported they are silently unselectable and the runtime falls back tophi3-mini. Nothing warns: an unset knob and a genuinely GPU-less machine are indistinguishable to the profiler.
| Variable | Meaning | Default when unset |
|---|---|---|
ASHLAR_TOTAL_VRAM_BYTES |
Total GPU memory, bytes. See the warning above. | 0 |
ASHLAR_AVAILABLE_VRAM_BYTES |
Free GPU memory, bytes. Falls back to ASHLAR_TOTAL_VRAM_BYTES when 0. |
0 |
ASHLAR_CPU_UTIL_PERCENT |
Current CPU utilisation, 0–100. | 0 |
ASHLAR_GPU_UTIL_PERCENT |
Current GPU utilisation, 0–100. | 0 |
ASHLAR_ON_BATTERY |
true when running on battery. |
false |
ASHLAR_CHARGING |
true when the battery is charging. |
false |
ASHLAR_BATTERY_PERCENT |
Battery charge, 0–100. | 100 |
ASHLAR_THERMAL_STATE |
ThermalState name: Nominal, Fair, Serious, Critical. |
Nominal |
ASHLAR_APP_STATE |
AppState name: Foreground, Background, Suspended. |
Foreground |
ASHLAR_USER_ACTIVE |
true when a user is actively at the machine. |
false |
ASHLAR_BACKGROUND_TASK_PERMISSION |
true when the OS permits background work. |
false |
ASHLAR_BATTERY_OPTIMIZATION_ENABLED |
true when the OS is throttling background work to save power. |
false |
ASHLAR_NETWORK_WIFI |
true when the active link is Wi-Fi. |
true |
ASHLAR_NETWORK_METERED |
true when the active link is metered. |
false |
ASHLAR_NETWORK_LATENCY_MS |
Average network latency, milliseconds. | 25 |
ASHLAR_STORAGE_AVAILABLE_BYTES |
Free disk, bytes. | 0 |
ASHLAR_STORAGE_TOTAL_BYTES |
Total disk, bytes. | 0 |
Defaults above are read from EnvironmentHardwareProfiler; a value that fails to parse falls back to
the same default rather than throwing.
Desktop NCR uses its own options-bound Ollama endpoint for model serving.
| Key / Variable | Description | Default |
|---|---|---|
Ashlar:NodeCapabilityRuntime:Ollama:BaseUrl (Ashlar__NodeCapabilityRuntime__Ollama__BaseUrl) |
NCR Ollama backend base URL used by desktop policy registrations. Overridden by ASHLAR_OLLAMA_BASE_URL; falls back to legacy OLLAMA_BASE_URL when unset (see resolution order above). |
http://127.0.0.1:11434 |
Behavior notes:
- On startup, NCR runs a health probe against the configured Ollama backend and logs a degraded warning if unreachable.
- A degraded startup does not crash the host; agentic tasks may escalate until Ollama becomes reachable.
- NCR records metrics for model resolution, model load, and Ollama endpoint latencies/error rates via
IMetricsCollectorkeys underncr.*.
Remote brick catalogs now use an in-memory stale capability snapshot fallback:
- Fresh
/api/capabilitiesresponses are cached per remote base URL. - If a later capability fetch fails, the last known manifest is reused and marked stale internally.
- Consumers should treat stale manifests as routing hints (not hard guarantees), and retry capability refresh periodically.
Ashlar:Execution:RemoteCapabilities:MaxStaleAge(Ashlar__Execution__RemoteCapabilities__MaxStaleAge) bounds stale fallback age (default00:10:00). If stale data exceeds this age, fallback is rejected.
Suggested starting SLOs/alerts using ncr.* metrics (these are IMetricsCollector keys; over OTLP they appear as operation / counter attribute values on ashlar.operation.duration / ashlar.operation.count — see "Observability" above and docs/NcrReleaseSLOs.md):
ncr.model_resolution.target.Escalate: alert if escalation ratio > 20% over 15 minutes for user-facing workloads.ncr.model_load.errorandncr.ollama.*.error: alert on sustained non-zero error rate over 5 minutes.ncr.ollama.chat.duration: track p95/p99; alert if p95 exceeds your interactive budget for 10+ minutes.ncr.profile.constraint_change: watch for bursty spikes that correlate with thermal/memory pressure and escalation increases.
Operational guidance:
- Treat stale capability fallback as degraded mode; prefer conservative routing and periodic refresh attempts.
- Set
ASHLAR_OBSERVATION_FAIL_OPEN=1for production-style hosts that must continue serving even if observation store permissions are restricted.
| Variable | Description | Default |
|---|---|---|
VIDEO_SERVICE_URL |
Video analysis service URL | required for video provider |
| Variable | Description | Default |
|---|---|---|
ASHLAR_TRUST_AUDIT_DB |
Path to LiteDB audit log | in-memory |
ASHLAR_KNOWLEDGE_LOG_PATH |
Path to user knowledge log | in-memory |
ASHLAR_ACCESS_BOUNDARY_CONFIG |
Path to access boundary JSON | unset |
ASHLAR_TRUST_POLICY_PACKS_PATH |
Directory containing trust policy pack JSON files | config/trust-packs (repo root) |
ASHLAR_ACTIVE_TRUST_POLICY_PACK_PATH |
Path to active pack selection file | active-pack.json in packs dir |
For a layered breakdown of mesh capabilities (identity, registry, transport, trust, request/fulfill, sync, WAN gaps), see MeshAgentSetupCapabilityBreakdown.md.
| Variable | Description | Default |
|---|---|---|
ASHLAR_MESH_DIR |
Root for this node's published packages and imported peer content. One level off yields an empty peer store with no error. | <state>/mesh |
ASHLAR_MESH_SERVE_BIND |
Optional bind address for mesh serve (ASHLAR_MESH_SERVE_PORT). On AirGapped and SecureWorkstation an unset value is any-interface (0.0.0.0) and boot fails; 127.0.0.1, localhost, bare/bracketed IPv6 loopback and IPv4-mapped loopback succeed. Other profiles keep ListenAnyIP and do not apply this value. |
unset |
ASHLAR_TAILNET_CMD |
Command invoked to enumerate tailnet peers. See docs/Federation.md (F4). |
unset (tailnet discovery off) |
ASHLAR_TAILNET_REFRESH_SECONDS |
How often the tailnet peer list is re-read. | 300 |
ASHLAR_MESH_PEER_ID |
Mesh peer identifier | random GUID |
ASHLAR_MESH_INSTANCES_PATH |
Path to file-based mesh instance registry | unset |
ASHLAR_TRUSTED_PEER_IDS |
Comma-separated peer IDs trusted for execution (mesh + RunPod/peer capability routing) | unset (all peers trusted) |
ASHLAR_UNTRUSTED_PEER_IDS |
Comma-separated peer IDs blocked from execution | unset |
ASHLAR_MESH_TRUST_POLICY |
Same variable as under Core: any, allowlist, trusted-only, trusted-preferred (unknown → trusted-preferred). There is no open/denylist value; use the peer-id lists above for that |
unset (any for discovery, trusted-preferred for capability requests) |
ASHLAR_PEER_TRUST_POLICY |
Fallback for ASHLAR_MESH_TRUST_POLICY when that is unset; same values |
unset |
ASHLAR_SHARED_ADAPTATIONS_PATH |
Path for shared adaptation artifacts across mesh | unset |
Generation execution routing uses NCR + peer network + RunPod cloud. These options are bound from Ashlar:RunPod:* and can be set with environment variables (__ separator).
| Key / Variable | Description | Default |
|---|---|---|
Ashlar:RunPod:ApiKey (Ashlar__RunPod__ApiKey) |
RunPod API key | empty |
Ashlar:RunPod:BaseUrl (Ashlar__RunPod__BaseUrl) |
RunPod API base URL | https://api.runpod.io |
Ashlar:RunPod:PreferredGpuTier (Ashlar__RunPod__PreferredGpuTier) |
Preferred GPU tier for cloud jobs | NVIDIA_A4000 |
Ashlar:RunPod:Timeout (Ashlar__RunPod__Timeout) |
Max remote job execution duration before timeout/teardown | 00:10:00 |
Ashlar:RunPod:PollingInterval (Ashlar__RunPod__PollingInterval) |
RunPod status polling interval | 00:00:02 |
Ashlar:RunPod:OutputStagingPath (Ashlar__RunPod__OutputStagingPath) |
Staged output path for remote artifacts | temp path (ashlar-runpod) |
Ashlar:RunPod:QueueDepthThreshold (Ashlar__RunPod__QueueDepthThreshold) |
Local queue threshold before remote routing | 4 |
Ashlar:RunPod:EnablePeerNetworkRouting (Ashlar__RunPod__EnablePeerNetworkRouting) |
Enables routing to peer Ashlar nodes | false |
Ashlar:RunPod:PreferPeerNetworkOverCloud (Ashlar__RunPod__PreferPeerNetworkOverCloud) |
System default preference when remote routing is required | true |
Ashlar:RunPod:PeerCapabilityId (Ashlar__RunPod__PeerCapabilityId) |
Capability identifier required for peer eligibility | generation.capability-routing |
Ashlar:RunPod:PeerRoutingBrickId (Ashlar__RunPod__PeerRoutingBrickId) |
Brick id invoked on peer nodes | generation.capability-routing |
Ashlar:RunPod:PeerRequestTimeout (Ashlar__RunPod__PeerRequestTimeout) |
Per-peer request timeout before failover | 00:00:30 |
Ashlar:RunPod:PeerDiscoveryInterval (Ashlar__RunPod__PeerDiscoveryInterval) |
Peer capability snapshot refresh interval | 00:00:10 |
Routing behavior:
CapabilityRoutingBrickis the default generation entry point.RemoteExecutionPreference(job-level) can force or prefer peer/cloud routing (UseSystemDefault,CloudOnly,PreferPeerNetwork,PeerNetworkOnly).- Peer execution includes candidate ranking, timeout handling, and failover across eligible peers.
See docs/runtime/ExecutionRouting.md for detailed execution flow and resilience behavior.
| Variable | Description | Default |
|---|---|---|
ASHLAR_EPHEMERAL |
1 = enable ephemeral models (Ollama in container) when supported |
unset |
ASHLAR_EPHEMERAL_MODELS |
1 = use ephemeral Ollama container for LLM; container removed when session ends |
unset |
ASHLAR_EPHEMERAL_DB |
postgres = use ephemeral Postgres container for workflows/tests |
unset |
ASHLAR_TEST_EPHEMERAL |
1 = run tests in ephemeral containers (no volume mounts) |
unset |
ASHLAR_TEST_NO_NETWORK |
1 = run tests with no network access. |
unset |
| Variable | Description | Default |
|---|---|---|
ASHLAR_CLEAN_BEFORE_TEST |
1 = run test-artifacts cleanup before ashlar test local |
unset |
ASHLAR_CLEAN_AFTER_TEST |
1 = run test-artifacts cleanup after ashlar test local |
unset |
ASHLAR_ARTIFACT_CLEANUP_REPO_ROOT |
Repo root for cleanup; unset = auto-detect | unset |
ASHLAR_INCOMPLETE_BLOB_PATH |
Path to content-addressed blob storage for incomplete-blobs strategy |
unset |
ASHLAR_BLOB_LIFECYCLE |
docker = pause Docker Desktop before incomplete-blob cleanup |
unset |
| Variable | Description | Default |
|---|---|---|
ASHLAR_BACKGROUND_AGENTS_CONFIG |
Path to background agent set JSON configuration | unset |
ASHLAR_AGENT_MODE_PATH |
Path to the file-based aggressiveness mode store (`{"Mode":"passive" | "semi-active" |
ASHLAR_OBJECTIVES_ROOT |
Objective store root ({status}/{id}.md + witness/proposal siblings); read by AddBackgroundAgents and the Runtime Studio path resolver |
<cwd>/.ashlar/runtime-studio/objectives |
ASHLAR_FORGE_ROOT |
Forge change-proposal queue root | <cwd>/.ashlar/runtime-studio/forge |
ASHLAR_FORGE_PROPOSED_TTL_HOURS |
How long a proposed change sits in the forge queue before expiry. | 72 |
ASHLAR_FORGE_APPROVED_TTL_HOURS |
How long an approved change stays applicable before expiry. | 168 |
ASHLAR_BUILD_BUDGET |
Wall-clock budget, seconds, for a build a policy will approve. Operator knob (BuildTestBudget). |
policy default |
ASHLAR_TEST_BUDGET |
Wall-clock budget, seconds, for a test run a policy will approve. Operator knob (BuildTestBudget). |
policy default |
ASHLAR_OBSERVATIONS_PATH |
Path to the shared observations.jsonl |
<cwd>/.ashlar/runtime-studio/observations.jsonl |
ASHLAR_CYCLE_EVENTS_PATH |
Path to cycles.jsonl (absolute or cwd-relative) |
<cwd>/.ashlar/runtime-studio/cycles.jsonl |
ASHLAR_DASHBOARD_AUTH_TOKEN |
Shared secret for ashlar background-agent dashboard (same as --auth-token); when set, requests need ?token= or a Bearer header |
unset (dashboard binds 127.0.0.1 only) |
ASHLAR_SANDBOX_ROOT |
Sandbox root for confined tool paths (PathAllowlist, forge propose-change) when the world snapshot carries no SandboxRoot. Must be a SIBLING of .ashlar/, never inside it — .ashlar/ is the governance and admission-ledger directory and is refused at the write edge; scripts/sandbox/init-agent-sandbox.sh creates <repo>/agent-sandbox/ |
unset |
ASHLAR_PATH_ALLOWLIST_EXTRA |
Comma/semicolon-separated extra path prefixes appended to the confined toolbox allowlist (defaults: src/, tests/, docs/, application/). Widening only — the default allowlist cannot be narrowed from here — and never onto a governance prefix: an entry such as .ashlar/host_apps/, scripts/, or any *.props is dropped at construction and reported on PathAllowlist.RejectedExtras, because the governance floor in ToolSandbox.TryResolveWritePath would refuse every write beneath it anyway |
unset |
ASHLAR_EXTENSION_MAX_LINEAGE_DEPTH |
Extender ceiling (SX-AUDIT invariant D): max ParentId hops below a human-authored root an extender may sit and still extend. May only LOWER the built-in default. |
1 |
ASHLAR_ANALYZER_SEVERITY_FLOOR |
Lowest analyzer severity that still refuses a candidate. May only TIGHTEN the built-in floor. | built-in floor |
ASHLAR_GENERATION_DEPTH_CEILING |
Max generations a single autonomous loop may produce. May only LOWER the built-in ceiling. | built-in ceiling |
ASHLAR_EXTENSION_MAX_UNATTENDED_CYCLES |
Extender ceiling: extend cycles since a human last armed the agent before it holds (re-arm: restart or RearmExtension). May only LOWER the default. |
8 |
ASHLAR_EXTENSION_MAX_CYCLES_PER_HOUR |
Extender ceiling: extend cycles in any trailing hour. May only LOWER the default. | 4 |
ASHLAR_OBSERVATION_DEGRADED_MODE |
1 = start observation pipeline in degraded mode |
unset |
ASHLAR_OBSERVATION_FAIL_OPEN |
1 = observation pipeline continues on store errors |
unset |
BING_SEARCH_KEY |
API key for Bing web search provider | unset (falls back to mock) |
Bound from the Ashlar:Autonomy section by AddAshlarAutonomy(configuration) — a host-composed surface: the shipped API/CLI hosts do not call it, so the configuration is whatever the composing host passes (the first-flight spike passes an in-memory set; a host that passes builder.Configuration gets appsettings.json + Ashlar__Autonomy__*). See AshlarAutonomyOptions for the full set and ValidateAshlarAutonomyOptions for what refuses to boot. Nothing runs unless Enabled=true.
Recommended trio for anything beyond local development: UseSandboxSessions=true, BuildCandidateInSession=true, ExecuteCandidateInSession=true, with a SessionImage (and, once you have read a certificate, its SessionImageDigest). All three default to false so that a bare AddAshlarAutonomy() stays valid — but with Enabled=true and the execution leg off, the witness and mutation legs run model-proposed candidate and mutant code in the host process; HoldAdmission blocks the swap, not the execution. AddAshlarAutonomy logs a warning at composition in that state, and the standing loop (AddAshlarAutonomyLoop) additionally refuses to hand a proposer any "repair" for the resulting in-process refusal.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar:Autonomy:Enabled (Ashlar__Autonomy__Enabled) |
Master switch. Off means the standing loop's timer never starts and validation always passes | false |
Ashlar:Autonomy:UseSandboxSessions (Ashlar__Autonomy__UseSandboxSessions) |
Open an attested container session per iteration. The standing loop builds a SessionSpec ONLY when this is true; requires SessionImage |
false |
Ashlar:Autonomy:BuildCandidateInSession (Ashlar__Autonomy__BuildCandidateInSession) |
Compile the candidate inside the session (session-build certificate input); iterations without a session then refuse fail-closed. Requires UseSandboxSessions |
false |
Ashlar:Autonomy:ExecuteCandidateInSession (Ashlar__Autonomy__ExecuteCandidateInSession) |
Run the witness, determinism and mutation legs inside the session (session-execution input) — untrusted candidate code never runs in-process. Requires BuildCandidateInSession |
false |
Ashlar:Autonomy:SessionImage (Ashlar__Autonomy__SessionImage) |
Container image proposal sessions start from; required when UseSandboxSessions=true. Must already be present on the engine — sessions run with --pull never and never fetch an image. The standing loop's own AutonomyLoopSettings.SessionImage takes precedence when set |
unset |
Ashlar:Autonomy:SessionImageDigest (Ashlar__Autonomy__SessionImageDigest) |
Optional pin on the session image's identity: the engine image ID (sha256:…) that SessionImage must resolve to — the same value attestation records and certificates carry as their image-digest input, so pin by copying it from a certificate you have read. When set, a session whose image resolves to anything else refuses to start (checked before docker run and again at attestation); a value not of the form sha256:… fails validation at boot |
unset (capture only) |
Ashlar:Autonomy:HoldAdmission (Ashlar__Autonomy__HoldAdmission) |
Certify fully, admit nothing without a human (CertifiedButHeld even at Tier 0). Enforced by the harness AddAshlarAutonomy composes; the standing loop reports this same value and has no dial of its own |
true |
Ashlar:Autonomy:IterationCeilingSeconds (Ashlar__Autonomy__IterationCeilingSeconds) |
Absolute per-iteration wall-clock ceiling. The standing loop's session keepalive is this plus a 60 s margin, so the ceiling — not the container — ends a runaway iteration | 600 |
Ashlar:Autonomy:CadenceFloorSeconds, RetentionWindow, Watch*, LineageDemotionThreshold, ReaperSweepSeconds, DigestIntervalSeconds, ThroughputGuardFactor |
Swap cadence, rollback retention, watch-window thresholds, demotion, reaper and digest cadence — see AshlarAutonomyOptions |
see class |
| Variable | Description | Default |
|---|---|---|
ASHLAR_CERT_DEV_HMAC_KEY |
HMAC key for signing and verifying brick and composition certification records (CertificationRecordSigner, CompositionCertificationRecordSigner, Ashlar.Certification.Contracts.CertificationRecordSigning). Unset means the COMMITTED, PUBLIC dev key CertificationRecordSigning.DefaultDevKey: anyone with the source can forge a record that verifies, so certificates then prove integrity against accident, not against an adversary. Both signers log a warning at construction while the dev key is in effect (UsesDevKey). Same-owner cross-project reuse works by sharing this value; cross-organization trust needs the Ed25519 key below or PKI |
unset (dev key; warns) |
ASHLAR_CERT_ED25519_KEY |
Base64 Ed25519 private key; when set, records are dual-signed and carry the public key. CertificationVerifyOptions.Default and .Strict require Ed25519 signatures (RequireEd25519Signature = true) and trust-loop schema v2+ (MinimumSchemaVersion = 2), fail-closed; only .Legacy accepts HMAC-only records (see limitations 7–8 CLOSED 2026-09-06 in docs/certification-evidence.md). Trusted-key pinning is now configurable — see ASHLAR_CERT_TRUSTED_ED25519_KEYS below — but it is still OPT-IN: with no pinning set configured, a self-consistent record signed with an attacker's own keypair still verifies (limitation 1: dev HMAC under committed key remains forgeable). Limitation 9 is CLOSED as of 2026-09-13: a CertificationRecordSigner holding a real key now keys the composition lane too, through the shipped DI registration. Production paths use Strict |
unset (HMAC-only) |
ASHLAR_CERT_TRUSTED_ED25519_KEYS |
Base64 raw 32-byte Ed25519 PUBLIC keys this host accepts as record signers, separated by commas, semicolons or whitespace (newlines included, so the list can be mounted as a multi-line secret). This is the switch that makes "signed" mean "signed by someone we accept": CertificationRecordEd25519.VerifySignature checks the signature against the public key the RECORD carries, so requiring a signature without pinning only forces an attacker to sign rather than strip. Read once per process by CertificationTrustPolicy.Ambient, which the record store, the certified brick registry, the hot-swap host, the composition constituent checker, the self-extend admission edge, the state-log verifier and the adaptation wiring all apply in place of the bare Strict preset. There is deliberately no configuration path here for a private key — that is the minting capability and belongs to the certifier. Present-but-unusable configuration (no keys, a blank entry, a non-Base64 value, a key that is not 32 raw bytes) throws CertificationTrustConfigurationException at resolution rather than degrading to an empty set, because an empty set reads as pinning OFF everywhere downstream. Keys are normalized to their canonical Base64 encoding, since enforcement is an ordinal string comparison against what the record carries |
unset (unpinned: any keypair verifies) |
ASHLAR_CERT_PINNING_REQUIRED |
Asserts that pinning must be in effect, so a deployment that expected it fails at startup instead of verifying unpinned for its lifetime. Accepts 1/true/yes/on and 0/false/no/off, case-insensitively. When it asserts pinning and ASHLAR_CERT_TRUSTED_ED25519_KEYS supplies no key, resolution throws. A value that is neither — a typo, or a deployment template that rendered the variable with nothing substituted into it — also throws, rather than being read as "not required": the switch that demands pinning must not be the thing that silently disables it. Only an ABSENT variable means "no assertion made" |
unset (no assertion) |
Kernel options (bound inside AddAshlar — environment variables only in the shipped hosts). See WorkloadScaling.md.
| Variable / config key | Description | Default |
|---|---|---|
Ashlar:WorkloadScaling:Provider (Ashlar__WorkloadScaling__Provider) |
null, kubernetes/k8s, or compose/docker-compose |
null (no-op scaler) |
Ashlar:WorkloadScaling:Enabled (Ashlar__WorkloadScaling__Enabled) |
Enable scaling actions | true |
ASHLAR_WORKLOAD_SCALER |
Env shortcut that overrides Provider after binding |
unset |
ASHLAR_WORKLOAD_SCALING_ENABLED |
1/true or 0/false — overrides Enabled after binding; any other value leaves it alone |
unset |
ASHLAR_WORKLOAD_AUTOSCALE |
1/true starts ElasticWorkloadAutoscaleService (Autoscale.Enabled) |
unset |
Host-owned options bound by Ashlar.API from builder.Configuration (appsettings.json and Ashlar__Mcp__* / Ashlar__A2A__* alike). All four surfaces are off by default and expose nothing until enabled AND allowlisted; the agent-server compose passes the four Enabled flags through as false. Full reference: docs/architecture/ProtocolIntegration-MCP-A2A.md.
| Section | Master switch | What else it needs |
|---|---|---|
Ashlar:Mcp:Server (AshlarMcpServerOptions) |
Enabled |
ExposedToolIds allowlist (empty = zero tools), ServerName, RepoRoot/OutputRoot, MaxConcurrentToolCalls, per-tool ArgumentOverrides |
Ashlar:Mcp:Client (AshlarMcpClientOptions) |
Enabled |
Servers[] (Name, Url, optional ApiKeyHeader + ApiKeyEnvVar — the secret lives in the named env var, never in config, AllowedTools), ConnectTimeout, ToolListRefreshInterval |
Ashlar:A2A:Server (AshlarA2AServerOptions) |
Enabled |
ExposedAgentIds allowlist, ExposeByCoordinationProtocol, PublicBaseUrl, PrimaryAgentId, AllowAnonymousAgentCard, DefaultExecutionTimeout |
Ashlar:A2A:Transport (A2ATransportOptions) |
Enabled |
Endpoints[] per remote URL prefix (API key env var names) |
| Variable | Description | Default |
|---|---|---|
ASHLAR_DAILIES_PATH |
Directory the Director dailies endpoints read (GET /api/director/dailies); read from the API's configuration, so the config key Ashlar:DailiesPath works too |
<app base directory>/dailies (/data/dailies in the agent-server compose) |
ASHLAR_LICENSE_FILE |
Path to the signed private-license JSON; overrides Ashlar:PrivateLicense:LicenseFilePath. Relative paths resolve against the content root |
unset (falls back to the config key) |
Bound from the Ashlar:Barriers section (appsettings.json or Ashlar__Barriers__* environment variables).
| Variable | Description | Default |
|---|---|---|
Ashlar__Barriers__Levels__0, __1, … |
Ordered barrier levels, lowest (floor) to highest | public, internal, confidential, restricted in Ashlar.API/appsettings.json |
Ashlar__Barriers__RequireExplicitBarrier |
true = an agent invocation with no explicit barrier context is refused and surfaces as escalation / errorCode BARRIER_CONTEXT_MISSING (the response says why 0 agent(s) executed); false = missing context defaults to the floor level and records a DefaultApplied audit event |
false (code default, Ashlar.API/appsettings.json, agent-server compose, CLI daemon) |
Ashlar__Barriers__HostCeiling |
Highest level this host may process; unset disables the ceiling check | confidential in Ashlar.API/appsettings.json |
Ashlar.API does not register an HTTP barrier-context middleware, so its requests never carry an explicit barrier context: leave RequireExplicitBarrier at false there, or opt in only from a host that establishes the context itself (IBarrierContextAccessor.Initialize, e.g. ashlar orchestrate --barrier <level>). HttpBarrierContextMiddleware (gated by ASHLAR_BARRIER_MIDDLEWARE_ENABLED) exists in Ashlar.Runtime but is not wired into any shipped host.
| Variable | Description | Default |
|---|---|---|
ASHLAR_ALLOW_MOCK |
1 = enable mock/offline/mock-json/echo providers |
unset |
ASHLAR_LOCAL_MODEL_PATH |
Path to local ONNX/LLamaSharp model for local provider |
unset |
ASHLAR_LOCAL_CONTEXT_SIZE |
Context window, tokens, for the local model. | model default |
ASHLAR_LOCAL_QUEUE_DEPTH |
Local execution queue depth for routing decisions | unset (auto) |
ASHLAR_GPU_COMPUTE_CLASS |
GPU compute class label for NCR capability matching | unset |
ASHLAR_LOAD_PREFERENCE |
Default load balancing preference | unset |
ASHLAR_EXECUTION_REMOTE_URL |
Remote execution endpoint URL for hosting | unset |
ASHLAR_LOCAL_MODEL_PATH alone is not enough to get the local provider: the GGUF weights are loaded by LLamaSharp.Backend.Cpu, which the Ashlar.* libraries reference with PrivateAssets="all" so that no consumer inherits its four same-named CPU-variant native payloads (they collide on dotnet publish -r <rid> with NETSDK1152). A deployable host opts in with its own <PackageReference Include="LLamaSharp.Backend.Cpu" Version="0.25.0" />. Every deployable host in this repo does: application/src/Ashlar.API and application/src/Ashlar.CLI (which is why the shipped images keep the capability), src/Ashlar.Mcp.Server.Host and src/Ashlar.Transport.Grpc.Server.Host (the two standalone hosts of docs/ProjectTiers.md, neither of which is built by a Dockerfile — whoever publishes them chooses the mode), and commercial/src/Ashlar.Commercial.Fleet.Host. Ashlar.Tests.Infrastructure carries the same line so the model-loading tests can run real inference. A host you add is not on that list until you put it there. Missing the opt-in is never a build error, and IsAvailable() does not detect it — it only checks that ASHLAR_LOCAL_MODEL_PATH names an existing file. With the variable unset (the shipped default) routing simply never selects local; with it set but no backend present, the first execution fails at LLamaWeights.LoadFromFile and surfaces as ModelUnavailableException wrapping the missing native library. See consumer-template/CONSUMING.md for the consumer-side snippet, including the CPU-variant prune a RID publish needs.
These back the default values of ashlar ci release-bundle command-line options
(RuntimeCommand.Configure.cs), so a flag always wins over the variable. Nothing in this repository
sets them: every CI invocation passes the thresholds explicitly. They exist for an operator running
scripts/release-preflight-local.sh who wants to tighten or relax a gate without editing a command
line, and they were undiscoverable until now — each appeared in exactly one file, its own read site.
| Variable | Backs | Default |
|---|---|---|
ASHLAR_RELEASE_PROVIDER |
--provider — model provider for the bundle run |
mock-json |
ASHLAR_RELEASE_MIN_PASS_RATE |
fallback for the core pass-rate floor | 0.85 |
ASHLAR_RELEASE_CORE_MIN_PASS_RATE |
core lane pass-rate floor (wins over the above) | ASHLAR_RELEASE_MIN_PASS_RATE, else 0.85 |
ASHLAR_RELEASE_MIN_TOTAL |
fallback for the core minimum sample size | 10 |
ASHLAR_RELEASE_CORE_MIN_TOTAL |
core lane minimum sample size (wins over the above) | ASHLAR_RELEASE_MIN_TOTAL, else 10 |
ASHLAR_RELEASE_HISTORY_WINDOW |
fallback for the core history window, runs | 20 |
ASHLAR_RELEASE_CORE_HISTORY_WINDOW |
core lane history window (wins over the above) | ASHLAR_RELEASE_HISTORY_WINDOW, else 20 |
ASHLAR_RELEASE_VISUAL_MIN_PASS_RATE |
visual lane pass-rate floor | 0.80 |
ASHLAR_RELEASE_VISUAL_MIN_TOTAL |
visual lane minimum sample size | 8 |
ASHLAR_RELEASE_VISUAL_HISTORY_WINDOW |
visual lane history window, runs | 20 |
ASHLAR_VISUAL_PROMOTION_STREAK |
consecutive visual passes required to promote | 3 |
ASHLAR_VISUAL_REQUIRED_MODE |
visual comparison mode | auto |
ASHLAR_RELEASE_LANE_REPETITIONS |
times each lane is repeated | 1 |
ASHLAR_RELEASE_SLO_NCR_RESOLUTION_MS |
NCR model-resolution SLO, ms | 250 |
ASHLAR_RELEASE_SLO_NCR_LOAD_MS |
NCR model-load SLO, ms | 1000 |
ASHLAR_RELEASE_SLO_NCR_OUTCOME_MS |
NCR outcome-recording SLO, ms | 1500 |
ASHLAR_RELEASE_SLO_NCR_FAILURE_RATE |
NCR failure-rate ceiling | 0.2 |
The three CORE_ variables each fall back to their un-prefixed sibling before the built-in default,
so setting ASHLAR_RELEASE_MIN_TOTAL alone moves the core lane and leaves the visual lane untouched.
Read by spikes/autonomy-first-flight/FirstFlight/SweepMode.cs, which is what the scheduled dogfood
canary runs. spikes/ is compiled by no solution, so these are exercised by the sweep and by
nothing else in CI — which is exactly why they were invisible.
| Variable | Meaning | Default |
|---|---|---|
ASHLAR_CAMPAIGN_DIR |
Where the sweep writes its campaign directory, including the certification evidence sidecar the ledger row cites. | <objectives-root>/../campaign |
ASHLAR_SWEEP_PROPOSER |
ollama for a live model proposer; anything else uses the recorded proposals beside each objective. |
unset (recorded proposals) |
ASHLAR_SWEEP_MAX_OBJECTIVES |
Stop after this many objectives. Ignored unless it parses to a positive integer. | unlimited |
ASHLAR_OLLAMA_MAX_TOKENS |
Token ceiling for the live proposer. Ignored unless positive. | provider default |
ASHLAR_OLLAMA_TIMEOUT_MINUTES |
Per-request timeout for the live proposer. Ignored unless positive. | provider default |
ASHLAR_OLLAMA_TEMPERATURE |
Sampling temperature for the live proposer. | provider default |
ASHLAR_OLLAMA_THINK |
true/false: enable the model's thinking mode. |
provider default |
ASHLAR_OLLAMA_SYSTEM_PREAMBLE_FILE |
Path to operator house rules handed to the proposer as data, never as a witness. Silently ignored when the file does not exist. | unset |
ASHLAR_OLLAMA_BASE_URL and ASHLAR_OLLAMA_MODEL are documented under the Ollama section above and
default to http://host.docker.internal:11434 and codellama:7b in this sweep.
Nothing in this repository reads this variable; several places deliberately clear it.
CompositionDogfoodHarness, four certification test fixtures and
tools/Ashlar.ExportCertifiedBrick all call
Environment.SetEnvironmentVariable("ASHLAR_CERT_NUGET_CONFIG", null) before doing work, so an
ambient value on the machine cannot influence a certification run's package resolution. It is listed
here because a knob that is silently neutralised should be discoverable — if you set it and nothing
happens, that is why.
~/.ashlar/config.json (or path from ASHLAR_CONFIG_PATH):
{
"provider": "openai",
"model": "gpt-4o-mini"
}provider:openai,azure,ollama,local,video,mock,offline,mock-json,echo(mock variants requireASHLAR_ALLOW_MOCK=1)model: override for the selected provider