Skip to content

Control Plane (Portal)

An optional client that pulls published policy from a central control plane and forwards audit records to it.

Off by default. Local and single-team use never needs it: the runtime is a local binary governed by local files, and nothing on this page is required to run anything.

The control plane server is not publicly available yet

The runtime ships the client — the portal: config block, the runtime portal commands, and the uplink behaviour described here. The server it talks to is a separate component that has not been released. This page exists because the config block, the environment variables and the runtime portal verb are all visible in a stock install, and you should know what they are and that leaving them off costs you nothing.

What it does, and what it deliberately does not

When enabled, exactly three things cross the network:

Direction Call When
Runtime → portal POST /api/v1/audit After every local audit write, best-effort
Runtime ← portal GET /api/v1/policies?active=true On runtime portal sync only — never implicitly
Runtime → portal POST /api/v1/fleet/heartbeat Once per process

What it is not matters more than what it is. Each of these preserves a property the runtime already had:

Not this Because
Not a replacement for the audit log logs/audit.log is written first, and its failure is fatal. The uplink is best-effort and its failure is discarded — enabling a portal cannot make a successful operation report as failed, and cannot lose your local trace
Not remote policy evaluation portal sync writes a local file and stops. Every decision is still made locally against that file, so a portal outage cannot change, widen, or block what a command may do
Not a second execution path No operation runs through the portal and no capability can address it. The only verbs are portal status and portal sync
Not automatic Policy is pulled only when you run portal sync. Bootstrap does not sync, and neither does any other command

Configuration

portal:
  enabled: false
  base_url: ""        # e.g. http://localhost:8080
  timeout_seconds: 5
Key Type Default Meaning
portal.enabled bool false Whether the client runs at all. When false, no control-plane request is ever made
portal.base_url string "" Control-plane origin, no trailing slash. Empty disables the client even when enabled: true
portal.timeout_seconds int 5 Bounds every portal call. Also the worst case an unreachable portal can add to a command — see Failure behaviour

Or without editing the file:

export RUNTIME_PORTAL_ENABLED=true
export RUNTIME_PORTAL_BASE_URL=http://localhost:8080
export RUNTIME_PORTAL_TIMEOUT_SECONDS=5

These three are the whole portal environment surface — see Environment Variables. Policy content always arrives over the API and is written to the policy file; there is no environment variable that injects a rule.

runtime portal status

A health check, like runtime auth status: no network request, no audit record. It answers "is this runtime pointed at a control plane, and which published policy is it running?"

runtime portal status
Portal: disabled (Community / local mode)

Enable in config.yaml:
  portal:
    enabled: true
    base_url: http://localhost:8080

Or:
  export RUNTIME_PORTAL_ENABLED=true
  export RUNTIME_PORTAL_BASE_URL=http://localhost:8080

Once enabled and synced:

Portal: enabled
  base_url: http://localhost:8080
  runtime:  engineering-runtime
  timeout:  5s
  policy:   v42
runtime portal status --output json
{
  "base_url": "http://localhost:8080",
  "configured": true,
  "enabled": true,
  "policy_version": "v42",
  "runtime": "engineering-runtime",
  "timeout_seconds": 5
}

configured is enabled && base_url != "" — the single condition that decides whether any request is attempted.

runtime portal sync

Fetches the published policy and writes it over your policy file, reporting the version applied and the path actually written.

runtime portal sync
Synced policy v42 → /Users/you/.engineering-runtime/policy-config.yaml
Local evaluation uses this file; a portal outage won't block commands.

The path is the real destination, so it follows RUNTIME_POLICY_FILE when a pipeline has relocated policy:

RUNTIME_POLICY_FILE=/ci/policy.yaml runtime portal sync --output json
{
  "path": "/ci/policy.yaml",
  "version": "v42"
}

Sync replaces your policy file — it does not merge

The published policy overwrites policy-config.yaml wholesale. Local rules are not merged in, no backup is taken, and turning the portal off afterwards does not restore the old file. Back it up first:

cp ~/.engineering-runtime/policy-config.yaml /tmp/policy-before-sync.yaml
runtime portal sync
# restore if the published policy is wrong:
# cp /tmp/policy-before-sync.yaml ~/.engineering-runtime/policy-config.yaml

The sync is audited

Unlike portal status, portal sync writes an audit record — on success and on failure:

runtime audit tail -n 1
[2026-02-11T18:41:31+05:30] success consumer=human  context=  transport=-  command=portal.sync  2ms  applied portal policy v42 to /Users/you/.engineering-runtime/policy-config.yaml

This is deliberate. portal sync rewrites the rules every later command is judged against, from a network source — and it is the one thing allowed to write policy-config.yaml, which the File Engine otherwise refuses precisely so an ordinary command cannot leave the next one ungoverned. The operation that changes what governance means is not allowed to be the one operation with no trace.

Failure behaviour

A portal problem never fails your command. The operation runs, the local audit record is written, and only the uplink is lost.

The cost of an unreachable portal is bounded twice over:

Bound Effect
timeout_seconds (default 5) No single portal call can hang longer than this
Per-process circuit breaker The first transport failure disables the uplink for the rest of that command

So a configured-but-down portal costs at most one timeout per command, not one per operation — a 20-step capability stalls once, not twenty times. Lower it further if that still matters in CI:

export RUNTIME_PORTAL_TIMEOUT_SECONDS=1

The circuit breaker applies only to the automatic uplink. portal status and portal sync always try and report the real error, because you asked for them explicitly.

There is no durable retry: records lost to an outage stay in your local audit log and are not resent later.

Security

There is no portal authentication yet

The client sends no API key and does not verify the server's identity. Anything answering base_url can replace your entire local policy on the next runtime portal sync.

Treat base_url as security-relevant configuration: point it only at a control plane you trust, prefer https outside local testing, and be aware that anyone able to set RUNTIME_PORTAL_BASE_URL in your environment can redirect where policy comes from.

Audit records leave your machine when the portal is enabled. They carry the command, the runtime context, the executor, status and result message — the same fields as your local log. Credentials are never included; the runtime does not store them and never logs full tokens.

Verifying where your policy came from

A synced policy file looks identical on disk to one you edited yourself, so runtime config validate names the source:

runtime config validate
Config:   /Users/you/.engineering-runtime/config.yaml
Policy:   /Users/you/.engineering-runtime/policy-config.yaml
Context:  /Users/you/.engineering-runtime/context.yaml (active: default)
Capabilities: /Users/you/.engineering-runtime/capabilities
Portal:   http://localhost:8080 (policy: v42)

With the portal off:

Portal:   disabled (local policy only)

That line is read from configuration and the portal-policy-version marker in the Runtime Homeconfig validate makes no network call.

Turning it off

unset RUNTIME_PORTAL_ENABLED RUNTIME_PORTAL_BASE_URL RUNTIME_PORTAL_TIMEOUT_SECONDS
runtime portal status

Or in config.yaml:

portal:
  enabled: false

Local audit continues exactly as before and nothing is sent anywhere.

Turning the portal off does not revert a synced policy

The file on disk stays whatever the last sync wrote. Restore your backup if you want the previous rules back.