Skip to content

Configure

The daemon reads ~/.post-cortex/daemon.toml at start. Environment variables override the file, and the file overrides the defaults.

~/.post-cortex/daemon.toml
host = "127.0.0.1"
port = 3737
data_directory = "/Users/you/.post-cortex/data"
Setting Environment Default
host PC_HOST 127.0.0.1
port PC_PORT 3737
data_directory PC_DATA_DIR ~/.post-cortex/data
auth_token PC_AUTH_TOKEN none
allowed_hosts PC_ALLOWED_HOSTS localhost, 127.0.0.1, ::1
log level RUST_LOG info

[[hooks]] and [[modules]] are reloaded without a restart: the daemon looks at the file every 2 seconds. Other settings take effect at the next start.

Bound to 127.0.0.1, only this computer reaches the daemon. To serve a tailnet or a LAN, bind to its address and set a token:

host = "100.64.0.7" # your tailnet address
auth_token = "<a long random string>"
allowed_hosts = ["my-mac.tailnet.ts.net"]
  • auth_token: every request except GET /health, and every gRPC call, must send Authorization: Bearer <token>. The pcx CLI reads it from the same file. Without a token on such an address, the daemon warns at start.
  • allowed_hosts: the names clients put in their URL. Requests with any other Host header get 403, which stops a web page from reaching the daemon through a DNS name that points at it. An entry is a name or an IP, with an optional port (my-mac.tailnet.ts.net:3737). The allowlist is not authentication: use the token too.

On by default. Before anything is stored, secrets and personal data are replaced with [REDACTED:<kind>]: private keys, JWTs, API keys of the common providers, bot tokens, bearer tokens, passwords in URLs, password= and similar values, emails, phone numbers and public IP addresses. Private and tailnet addresses, UUIDs and host names are kept: they are needed to connect events.

redact = true # PC_REDACT=0 turns it off
redact_patterns = ['ORD-\d{6}'] # your own, replaced as [REDACTED:custom]

The same value is always replaced the same way, so entities and relations stay consistent. Only counts per kind are logged, never the values.

search_mode = "local" # fast, local or accurate: the default when a call names none
neighbour_radius = 1 # records on each side of a hit; 0 for hits only
reranker_model = "cross-encoder/ms-marco-MiniLM-L4-v2" # the local reranker
voyage_api_key = "pa-..." # the accurate reranker, or VOYAGE_API_KEY
voyage_model = "rerank-3-lite"

What the modes do and what each costs: Search. With voyage_api_key set, Voyage is available but off: each workspace turns it on (the app’s workspace settings, or gRPC UpdateWorkspace with remote_rerank). Only then does an accurate search there send its query and candidate texts to Voyage AI; elsewhere it uses the local reranker. On Linux the local reranker needs ONNX Runtime 1.31; point ORT_DYLIB_PATH at libonnxruntime.so. Without it, the daemon logs an error and searches without the local reranker.

Records exist only when your agent writes them. Capture keeps the conversation too: every prompt and final answer, in a stream of the session apart from the records. It is off by default; pcx setup --capture turns it on for your clients. What it is for: Conversations.

message_retention_days = 90 # older turns are deleted with their vectors; 0 keeps them

The daemon stores everything in RocksDB in data_directory by default. Built with SurrealDB, it can use a SurrealDB server instead; a user other than root signs in as a namespace user, so the daemon does not need root:

USE NS post_cortex;
DEFINE USER pcx ON NAMESPACE PASSWORD '...' ROLES OWNER;

Move data between the two with pcx migrate.

Files, a shell and git are built in, each off until [[builtins]] names it with what it may reach. Everything a module touches goes through the daemon’s sandbox: files only below its filesystem roots, processes under the operating system’s sandbox (Seatbelt on macOS, Landlock and seccomp on Linux), and the network only with the network grant.

[[builtins]]
id = "tools-fs" # read, write, edit, list_dir, grep, glob
grants = ["filesystem"]
filesystem = [{ path = "/Users/you/project", write = true }]
[[builtins]]
id = "tools-git" # status, diff, log, branches, switch, commit
grants = ["filesystem", "spawn"]
filesystem = [{ path = "/Users/you/project", write = true }]
config = { author_name = "Agent", author_email = "agent@example.com" }
[[builtins]]
id = "board"

Every module has its page under Modules, with its settings. A module of your own runs as a process; see writing a module.

[[modules]]
id = "my-module"
command = "/opt/pcx/my-module"
sha256 = "<shasum -a 256 my-module>"
grants = ["write_memory"]
uses = ["post-cortex.memory"]

The daemon refuses a module whose file does not match its sha256, and one that asks for a grant its entry does not list. uses names the modules whose tools it may call.

The daemon runs your own hook scripts on your agents’ events, with Claude Code’s contract: the event as JSON on stdin, exit 0 with an answer, or exit 2 to block with stderr as the reason.

[[hooks]]
id = "secrets-guard"
command = "/Users/you/hooks/secrets-guard.py"
sha256 = "<shasum -a 256 secrets-guard.py>"
events = [{ event = "PreToolUse", policy = "fail-closed", deadline_ms = 2000 }]
capabilities = ["read_conversation"]

Only a fail-closed script may block; a fail-open one that fails or runs late is skipped. A script sees the conversation only with read_conversation.