Configuration

Default path: ~/.config/navra/config.toml

Server

[server]
socket = "/run/user/1000/navra/navra.sock"
tcp = "127.0.0.1:9315"
hook_timeout_secs = 10
mcp_version = "2026-07-28"
agent_signature_policy = "warn"
config_watch = false
config_watch_debounce_ms = 50
FieldTypeDefaultDescription
socketstring$XDG_RUNTIME_DIR/navra/navra.sockUnix socket path
tcpstring--TCP listen address (used instead of socket when set)
hook_timeout_secsu6410Per-hook timeout in seconds
mcp_versionstring"2026-07-28"MCP protocol version (2026-07-28 or 2025-03-26)
agent_signature_policystring"warn"Bundle signature policy: enforce, warn, skip
ws_ping_interval_secsu6430WebSocket ping interval
ws_idle_timeout_secsu64600WebSocket idle timeout (10 min)
config_watchboolfalseWatch config file for changes and hot-reload
config_watch_debounce_msu6450Debounce interval for config file watch events

PII models

[server]
pii_model_path = "~/.local/share/navra/models/pii-ner"
pii_multilingual_model_path = "~/.local/share/navra/models/pii-ner-multilingual"

Install PII NER models with navra pii download (English) or navra pii download --multilingual.

Container settings

[server]
containerized = true              # true/false/absent (auto-detect)
allow_direct_execution = false    # allow unsandboxed execution when no runtime found
agent_image = "localhost/navra-agent:latest"
model_server_image = "ghcr.io/ggerganov/llama.cpp:server-cuda"
container_memory = "2g"
container_cpus = "2"
container_pids = 256
openshell_gateway = "unix:///run/openshell/gateway.sock"

Identity and discovery

[server.identity]
key_path = "~/.config/navra/root.key"   # Ed25519 seed file (or OS keyring)
token_ttl = 3600                         # capability token TTL in seconds
max_delegation_depth = 3
nonce_cache_ttl_secs = 7200

[server.discovery]
url = "https://tools.example.com/mcp"
mdns = true
auth = "pat"
description = "Code analysis tools"
docs_url = "https://docs.example.com"
timeout_secs = 10
mdns_browse_secs = 3

Permissions

Permission sets define what agents can do. Each set specifies allowed operations, tools, paths, and safety profiles.

[permissions.default]
operations = ["read", "search", "list"]
tools = ["file_tree", "file_read", "file_grep"]

[permissions.developer]
operations = ["read", "write", "search", "list"]
tools = ["file_tree", "file_read", "file_write", "file_edit", "file_grep"]
paths = ["/home/user/projects"]
safety = "standard"
FieldTypeDefaultDescription
ringu8--Privilege ring (0 = most, 3 = least privileged)
allowstring[][]Allowed file path globs
denystring[][]Denied file path globs (deny wins)
operationsstring[][]Allowed operation namespaces
approvestring[][]Operations requiring human approval
safetystring"standard"Safety profile (see below)
default_tool_policystring"allow"Default for unmatched tools: allow, deny, approve
can_delegateboolfalseWhether agents can delegate capabilities

Safety profiles

ProfileDescription
standardRegex-based secret and PII detection
secrets-onlyOnly detect secrets (API keys, passwords)
pseudonymizeReplace PII with pseudonyms
blockBlock content containing PII or secrets
multi-labelMulti-label classifier with per-category thresholds
guardianGuardian HAP safety model
guardian-deepGuardian with deeper analysis
noneNo content filtering

Safety thresholds (multi-label)

[permissions.dev.safety_thresholds]
harm = 0.7
jailbreak = 0.9
pii = 0.5
refusal = 0.8

Custom safety patterns

[[permissions.dev.safety_patterns]]
category = "internal-url"
pattern = "https?://internal\\.example\\.com/.*"

Rate limiting

[permissions.agent]
rate_limit = "60/60"   # 60 tool calls per 60-second window

The format is <calls>/<seconds>. When the limit is exceeded, tool calls are rejected until the window resets.

Tool rules

[permissions.developer]
default_tool_policy = "deny"
tool_rules = [
  { tool = "file_read", policy = "allow" },
  { tool = "file_write", policy = "approve" },
  { tool = "shell_*", policy = "deny" },
]

Tool name patterns support glob matching (* suffix).

Domain rules

Semantic domain-based access control, evaluated before tool rules:

[permissions.readonly]
domain_rules = [
  { domain = "filesystem", operations = ["read"] },
  { domain = "git", operations = ["read"] },
  { domain = "shell", operations = [] },      # deny all shell
  { domain = "*", operations = ["read"] },     # default for unlisted domains
]

Tool classification overrides

Override auto-classification for specific tools:

[permissions.readonly.tool_class]
zip_files = { domain = "filesystem", operation = "write" }

IFC (Information Flow Control)

[permissions.dev]
tainted_write_policy = "approve"   # "allow", "approve", or "deny"
trusted_paths = ["~/Code/myproject/**", "~/Documents/**"]

When an agent reads external data (taint rises to Untrusted), the tainted_write_policy controls whether subsequent writes are allowed. Paths in trusted_paths keep their Trusted integrity label.

Tool disclosure

Control which tools appear in tools/list responses (UI-level only):

[permissions.limited]
tool_disclosure_include = ["file_*", "rag_*"]
tool_disclosure_exclude = ["file_delete"]

Egress filtering

[permissions.sandboxed]
egress_deny_all_external = true
egress_allowed_domains = ["api.github.com", "*.googleapis.com"]
egress_blocked_domains = ["evil.example.com"]

DMN decision tables

Business-rule guardrails authored as standard DMN decision tables.

[permissions.regulated]
dmn_policies = "policies/example-guardrails.dmn"
dmn_decision = "Tool Access"

The decision table is evaluated as an additional policy gate after TOML rules and Cedar policies. Business analysts can author rules using any DMN 1.3+ editor (Camunda Modeler, Trisotech, etc.).

See the [DMN guardrails guide]({{< relref "/docs/guides/dmn-guardrails" >}}) for details on authoring decision tables.

Compliance tags

[permissions.hipaa]
compliance = ["SOC2-CC6.1", "EU-AI-Act-Art-14", "HIPAA-164.312"]

Informational tags logged at startup for audit trail.

PII patterns (global)

Custom PII patterns applied globally across all safety pipelines:

[[pii_patterns]]
name = "employee-id"
regex = "EMP-[0-9]{6}"
category = "employee-id"

Categories defined here are treated as PII for IFC labeling.

Agents

Agent definitions bind a name and permission set to an identity.

[[agents]]
name = "claude"
token_hash = "sha256_hash_of_token"
permissions = "developer"
FieldTypeDefaultDescription
namestring--Unique agent identifier
token_hashstring--BLAKE3 hash of the agent's bearer token
permissionsstring--Permission set name from [permissions]
signing_keystring--Ed25519 key path for git commit signing
pubkeystring--Ed25519 public key for capability token auth
didstring--DID:key identifier (alternative to pubkey)
capability_tokenboolfalseEnable capability token issuance
token_ttlu64--Override token TTL for this agent (seconds)
modelstring--Model config key for per-agent routing on /v1 proxy

Generate a token:

navra token generate --name claude --permissions developer

Agents authenticate via Authorization: Bearer <token> or x-api-key: <token> (for Anthropic SDK clients like Claude Code that send ANTHROPIC_API_KEY as x-api-key). Both headers feed into the same constant-time BLAKE3 hash comparison.

Model Proxy

The gateway exposes two model proxy endpoints at /v1. All requests go through agent authentication, safety filtering, blackbox audit, and token metering. Any application that speaks the OpenAI or Anthropic API can use navra as its model endpoint.

Setup (common to both endpoints)

1. Define a model entry pointing to your upstream provider:

# Vertex AI — global endpoint (routes to nearest region)
[models.vertex-claude]
task = "chat"
base_url = "https://aiplatform.googleapis.com/v1/projects/MY_PROJECT/locations/global/publishers/anthropic/models"
locality = "remote"

# Vertex AI — regional endpoint (pin to a specific region)
# [models.vertex-claude]
# task = "chat"
# base_url = "https://us-east5-aiplatform.googleapis.com/v1/projects/MY_PROJECT/locations/us-east5/publishers/anthropic/models"
# locality = "remote"

# Or: direct Anthropic API
[models.anthropic]
task = "chat"
base_url = "https://api.anthropic.com"
api_key = "sk-ant-..."
locality = "remote"

# Or: local Ollama (default if no model entry)
[models.local]
task = "chat"
source = "ollama://gemma4:e4b"

2. Generate a token for the application connecting to navra:

navra token generate --name my-app --permissions dev
# Output: Token: mcd_abc123...  Hash: e35f...

3. Create an agent entry with the hash and model reference:

[[agents]]
name = "my-app"
token_hash = "e35f..."   # from step 2
permissions = "dev"
model = "vertex-claude"  # points to the model entry from step 1

4. Point your application at http://localhost:9315/v1 using the token from step 2 as the API key.

OpenAI-compatible (/v1/chat/completions)

Forwards OpenAI Chat Completions format. Works with any OpenAI SDK client, LangChain, LiteLLM, or custom code. Without a per-agent model entry, defaults to Ollama at localhost:11434.

When the agent's model entry points to a Vertex AI upstream, navra translates the OpenAI request to the Anthropic Messages format, forwards it to Vertex, and translates the response back to OpenAI format. The model_name in the model config entry sets the Vertex model (using the name@date format); the client's model field is ignored.

# Python (openai SDK)
OPENAI_BASE_URL=http://localhost:9315/v1 \
OPENAI_API_KEY=mcd_abc123... \
python my_app.py

# navra run (built-in agent runner)
MCPD_TOKEN=mcd_abc123... navra run "Summarise the last 5 emails"

# curl
curl http://localhost:9315/v1/chat/completions \
  -H "Authorization: Bearer mcd_abc123..." \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-haiku-4-5@20251001", "max_tokens": 100, "messages": [{"role": "user", "content": "hello"}]}'

Example: OpenAI-compatible agent using Claude via Vertex AI

# Model entry — pins to a specific Claude model on Vertex
[models.vertex-claude]
task = "chat"
base_url = "https://aiplatform.googleapis.com/v1/projects/MY_PROJECT/locations/global/publishers/anthropic/models"
model_name = "claude-sonnet-4-5@20250929"
locality = "remote"

# Agent identity
[[agents]]
name = "my-agent"
token_hash = "..."        # navra token generate --name my-agent --permissions dev
permissions = "dev"
model = "vertex-claude"   # routes /v1/chat/completions to Vertex
# Generate token
navra token generate --name my-agent --permissions dev

# Run with navra's built-in agent runner (model name, not config key)
MCPD_TOKEN=mcd_... navra run -m claude-sonnet-4-5@20250929 "Summarise the latest reports"

# Or point any OpenAI SDK client at navra
OPENAI_BASE_URL=http://localhost:9315/v1 \
OPENAI_API_KEY=mcd_... \
python my_app.py

Anthropic Messages API (/v1/messages)

Forwards the Anthropic Messages API format as-is, preserving thinking blocks, multi-part content, cache_control, and tool use.

# Claude Code
ANTHROPIC_BASE_URL=http://localhost:9315/v1 \
ANTHROPIC_API_KEY=mcd_abc123... \
claude

# curl
curl http://localhost:9315/v1/messages \
  -H "x-api-key: mcd_abc123..." \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{"model": "claude-sonnet-4-20250514", "max_tokens": 100, "messages": [{"role": "user", "content": "hello"}]}'

Vertex AI authentication

For Vertex AI upstreams (detected by googleapis.com in the base_url), navra obtains OAuth tokens automatically from Google Application Default Credentials (~/.config/gcloud/application_default_credentials.json). Run gcloud auth application-default login once to set this up.

Alternatively, set api_key in the model config to a Google OAuth token (must be refreshed manually before expiry).

Upstream MCP Servers

Connect external MCP servers through the gateway's security pipeline.

[[upstream]]
name = "github"
transport = "stdio"
command = ["npx", "-y", "@modelcontextprotocol/server-github"]
env = { GITHUB_TOKEN = "${credential:github_token}" }

[[upstream]]
name = "jira"
openapi = "https://jira.example.com/v3/api-docs"
[upstream.auth]
bearer = "${JIRA_TOKEN}"
tool_filter = ["get_*", "*_search"]

[[upstream]]
name = "remote-server"
transport = "http"
url = "http://localhost:3200/mcp"
request_timeout_secs = 60
FieldTypeDefaultDescription
namestring--Upstream identifier
transportstring"stdio"Transport: stdio, http, sse
commandstring[][]Stdio server command and arguments
cwdstring--Working directory for stdio transport
urlstring--URL for http/sse transport
enabledbooltrueEnable or disable this upstream
request_timeout_secsu6445Request timeout
retry_base_delay_msu641000Retry base delay
retry_max_delay_msu6430000Maximum retry delay
retry_budget_secsu64600Total retry budget
tool_filterstring[][]Glob patterns to filter exposed tools
tool_overridesmap{}Per-tool operation overrides: read, write, deny
max_response_bytesusize32768Max response body size for OpenAPI upstreams
openapistring--OpenAPI 3.x spec URL or file path
envmap{}Environment variables (${credential:label} for keyring)
credentialsmap{}Env var name to keyring label mappings

Upstream tool classification

[upstream.tool_class]
zip_files = { domain = "filesystem", operation = "write" }

OpenAPI authentication

[[upstream]]
name = "jira"
openapi = "https://jira.example.com/v3/api-docs"

[upstream.auth]
bearer = "${JIRA_TOKEN}"
# Or API key:
# api_key_name = "X-API-Key"
# api_key_value = "${API_KEY}"
# api_key_location = "header"   # or "query"
# Or basic auth:
# basic_username = "user"
# basic_password = "${PASSWORD}"

Upstream OAuth 2.1

[[upstream]]
name = "secure-server"
transport = "http"
url = "https://mcp.example.com/mcp"

[upstream.oauth]
client_id = "navra-client"
client_secret = "${OAUTH_SECRET}"
flow = "auto"     # "auto", "code", "client_credentials", "device"
scopes = ["read", "write"]

Network policy (sandboxed upstreams)

[[upstream]]
name = "restricted-server"
command = ["python3", "-m", "server"]

[upstream.network]
deny_all_external = true
allowed_domains = ["*.googleapis.com"]
blocked_domains = ["evil.example.com"]
allowed_ips = ["10.0.0.0/8"]

Modules

File module

[modules.file]
enabled = true
db = "~/.local/share/navra/index.db"
default_root = "~/Code"
watch = ["~/Code/myproject"]

Git module

[modules.git]
enabled = true

RAG module

[modules.rag]
enabled = true
db = "~/.local/share/navra/rag.db"
reranker_model_path = "~/.local/share/navra/models/reranker/model.onnx"
reranker_tokenizer_path = "~/.local/share/navra/models/reranker/tokenizer.json"
query_cache_ttl_secs = 300
query_cache_max_entries = 1000
FieldTypeDefaultDescription
enabledbooltrueEnable the RAG module
dbstring~/.local/share/navra/rag.dbSQLite database path
reranker_model_pathstring--ONNX cross-encoder model for reranking
reranker_tokenizer_pathstring--Tokenizer for the reranker model
query_cache_ttl_secsu64300Query cache TTL (0 = no caching)
query_cache_max_entriesusize1000Maximum cached query entries

Memory module

[modules.memory]
pii_filter = "standard"
retention_days = 90
pii_retention_days = 30
audit_retention_days = 365
auto_distill = true
FieldTypeDefaultDescription
pii_filterstring"standard"PII filter profile: standard, secrets-only, none
retention_daysu32--Auto-delete knowledge entries after N days
pii_retention_daysu3230Stricter TTL for PII-flagged entries
audit_retention_daysu32365Audit log retention
auto_distillbooltrueDistill facts from conversations on session end

Voice module

[modules.voice]
enabled = true
asr_model = "asr"
tts_model = "tts"
vad_threshold = 0.01
max_record_secs = 30
silence_timeout_ms = 1500
voice = "af_heart"

Vision module

[modules.vision]
enabled = true
model = "vision"

Registry module

[modules.registry]
enabled = true
cache_ttl_secs = 3600

Models

Model configuration for local and remote backends.

[models.embed]
model_path = "~/.local/share/navra/models/granite-embed/model.onnx"
tokenizer_path = "~/.local/share/navra/models/granite-embed/tokenizer.json"
task = "embedding"
dimensions = 768

[models.granite-chat]
source = "ollama://granite3.3:8b"
task = "chat"
runtime = "auto"
context_size = 8192

See Model server for the full field reference, runtime options, and speculative decoding configuration.

Model Server

When set, the gateway connects to an external model server instead of loading models in-process.

model_server = "http://127.0.0.1:9316"

Start the server with navra model serve. See the Model server guide for deployment details.

Budget

Resource limits for agent teams and flow execution.

[budget]
max_agents = 50
max_depth = 5
timeout_secs = 3600
max_iterations = 200
max_parallel = 2
max_tool_output_tokens = 0
truncation_strategy = "head_tail"
head_ratio = 0.7
max_tokens_per_run = 500000
checkpoint = true
checkpoint_db = "~/.local/share/navra/checkpoints.db"
FieldTypeDefaultDescription
max_agentsu3250Total agents across all teams/subflows
max_depthu325Escalation nesting depth
timeout_secsu643600Timeout per flow tree (30 min)
max_iterationsusize200ReAct iterations per agent
max_parallelusize2Concurrent agents (GPU bound)
max_tool_output_tokensusize0Tool output token limit (0 = unlimited)
truncation_strategystring"head_tail"truncate, head_tail, summarize
head_ratiof320.7Head ratio for head_tail truncation
max_tokens_per_runu64--Total token circuit breaker per agent run
compression_start_ratiof32--Context fill ratio to start compressing tool output
compaction_keep_recentusize--Recent items kept verbatim during compaction
compaction_trigger_ratiof32--Context fill ratio to trigger conversation compaction
checkpointboolfalseEnable SQLite checkpointing for crash recovery
checkpoint_dbstring~/.local/share/navra/checkpoints.dbCheckpoint database path

Approval

Human-in-the-loop approval workflow.

[approval]
timeout_secs = 300
grant_ttl_secs = 300
notify = "dbus"
FieldTypeDefaultDescription
timeout_secsu64300Timeout for human approval responses
grant_ttl_secsu64300TTL for cached approval grants
notifystring"dbus"Notification backend: dbus or none

Monitoring

Detect-only agent that observes tool calls without blocking.

[monitoring]
enabled = true
buffer_size = 256
FieldTypeDefaultDescription
enabledboolfalseEnable the monitoring agent
buffer_sizeusize256Escalation channel buffer size

Statistical Guardrails

Anomaly detection for agent behavior using statistical signals.

[statistical]
enabled = true
cosine_window = 50
cosine_z_threshold = 3.0
entropy_window = 20
entropy_min = 0.5
entropy_max = 4.0
block_on_anomaly = false
transition_window = 50
transition_min_observations = 10
FieldTypeDefaultDescription
enabledboolfalseEnable statistical guardrails
cosine_windowusize50Sliding window for cosine drift detection
cosine_z_thresholdf643.0Z-score threshold for anomaly detection
entropy_windowusize20Sliding window for entropy monitoring
entropy_minf640.5Minimum acceptable entropy (below = fixation)
entropy_maxf644.0Maximum acceptable entropy (above = scatter)
block_on_anomalyboolfalseBlock tool calls on anomaly (vs. warn)
transition_windowusize50Window for tool-transition anomaly detection
transition_min_observationsusize10Minimum observations before flagging

Temporal Contracts

Trajectory-level behavioral contracts that enforce ordering and frequency constraints on tool calls.

[temporal_contracts]
enabled = true
max_history_per_session = 200

[[temporal_contracts.contracts]]
name = "read-before-write"
description = "Must read a file before writing"
predicate = { type = "requires", tool = "file_write", prerequisite = "file_read" }
action = { type = "block", value = "Read the file first" }
applies_to = ["*"]
FieldTypeDefaultDescription
enabledboolfalseEnable temporal contracts
max_history_per_sessionusize200Action history entries per session
contractsarray[]List of contract definitions

Cost-Aware Routing

Route tool calls to different model tiers based on complexity.

[routing]
enabled = true
default_tier = "medium"

[[routing.tiers]]
name = "small"
model = "granite-2b"
max_tokens = 2048
patterns = ["file_read", "file_tree", "file_grep"]

[[routing.tiers]]
name = "medium"
model = "granite-8b"
max_tokens = 4096
patterns = ["*"]

[[routing.tiers]]
name = "large"
model = "granite-34b"
max_tokens = 8192
patterns = ["code_review_*", "planning_*"]
FieldTypeDefaultDescription
enabledboolfalseEnable cost-aware routing
default_tierstring"medium"Default tier for unmatched tools
tiersarray[]Ordered tier definitions

Each tier specifies:

FieldTypeDescription
namestringTier name for logging
modelstringModel identifier from [models.*]
max_tokensusizeMaximum output tokens for this tier
patternsstring[]Tool name glob patterns that route here

Credentials

Credential label to backend source mappings. Only credentials listed here are accessible to agents.

[credentials]
github_token = { source = "keyring", label = "navra/github" }
api_key = { source = "env", var = "MY_API_KEY" }

Cognitive Core

cognitive_core = "~/.config/navra/cognitive_core"

Path to the directory containing personas, heuristics, and directives.

Flow Directories

flow_dirs = ["~/.config/navra/flows", "/etc/navra/flows.d"]

Directories containing flow TOML files for DAG-based multi-agent orchestration.

gRPC Modules

Out-of-process gRPC modules:

[[grpc_modules]]
name = "custom-tool"
address = "unix:///run/navra/custom.sock"

Enterprise Auth

Enterprise-managed authorization via ID-JAG (corporate IdP integration):

[enterprise_auth]
issuer = "https://idp.example.com"
audience = "navra"

Operator Libraries

Drop TOML fragments into library directories for config composition.

[libraries]
library_dirs = ["~/.config/navra/libraries", "/etc/navra/libraries.d"]

Library files in these directories are deep-merged into the main config at startup. Main config wins on key conflicts. Duplicate keys across libraries produce a startup error.

See navra config list-libraries to inspect installed libraries.

Discovery

Agent discovery via DNS-AID or mDNS.

discover = ["example.com", "tools.internal.net"]

Domains to query for AID upstream discovery at startup.

Registry

Whitelisted MCP servers for the registry endpoint.

[[registry]]
name = "community"
url = "https://registry.mcp.run/mcp"
registry_type = "mcp"
remote_type = "streamable-http"

[[registry]]
name = "custom-http"
url = "https://registry.example.com"
registry_type = "http"
search_url = "https://registry.example.com/api/search?q={query}"
results_path = "data.results"
FieldTypeDefaultDescription
namestring--Server name (unique)
urlstring--Remote endpoint URL
registry_typestring"mcp"mcp, http, aws_agent_registry
remote_typestring"streamable-http"Transport: streamable-http, sse, stdio
descriptionstring--Human-readable description
repositorystring--Repository URL
search_urlstring--URL template for search ({query} placeholder)
results_pathstring--JSON path to extract results from HTTP response