Overview

Trust the execution layer, not just the model's judgment

Security Model#

flowchart LR
  Intent["Model tool request"] --> Permission["Role permission"]
  Permission --> Safety["Safety check"]
  Safety -->|"low risk"| Execute["Execute"]
  Safety -->|"needs confirmation"| Approval["Approval"]
  Approval -->|"allow"| Execute
  Approval -->|"deny"| Stop["Stop"]
  Execute --> Recovery["Checkpoint and event log"]

Trust boundaries#

Boundary Rule
Workspace Resolves relative, absolute, and nonexistent target paths through one unified path check
Project configuration An untrusted Workspace never loads project TOML
Project MCP An untrusted Workspace never loads it; stdio also needs its own execution approval
External files Require native file-picker authorization, scoped to the exact path
Model calls Always go through MRM and the Auth store
Tool calls Always go through role permissions, Safety, and Approval
Bot execution Fixes a revision and permission snapshot; never inherits a peer's permissions
Network requests URLs and the actual connection address are checked; Browser routes all traffic through a controlled proxy

Credentials#

API keys and OAuth tokens live in local, permission-restricted files. Credentials never enter project TOML, Session prompts, documents, or Git.

Provider credentials can come from an official client's local login state, or from configuration you enter explicitly. The model only ever sees a call's result — never the raw secret.

The native file picker also refuses to hand over credential directories or common secret files, including kxen's own data directory, Keychain, .ssh, .gnupg, .aws, .kube, .docker, various agent config directories, and .p8/.p12/.pfx/keychain files. Nonexistent targets are canonicalized through their nearest existing parent directory, so symlinks and .. can't escape the Workspace boundary.

Least privilege#

Research and planning roles default to read-only. Writing files, running commands, and deleting data require the execution role. MCP tools can additionally be configured per-item as allow, ask, or deny.

Bot capability, Workspace paths, and Connector grants are all explicitly granted by the owner before publish. A Bot's self-builder, Bot Message, CollaborationTask, Memory, and ArtifactRef can never expand permissions on their own — the self-builder can only edit its own Bot's design-time draft; it can't run tools, grant itself access, or publish. MCP tools also need an exact server grant and a Connector bound to the Workspace; Bots currently don't expose LSP, because cross-file location results can't yet be filtered per Bot path grant.

Shell commands run on your host machine — kxen doesn't claim to sandbox the process. It doesn't load your shell rc files or alias snapshots, and every command (plus every background-task start/restart) requires a fresh Approval showing the full command and working directory. A denial, or the absence of an approval channel, fails closed.

Hard protections#

Safety hard-denies operations against system paths, credential directories, Git metadata, disk operations, and large-scale resource destruction. Project Rules and user prompts can't override these.

Recoverability#

  • File and Session deletion go to the system trash.
  • Checkpoint uses an independent shadow Git repository.
  • Worktree isolates parallel edits.
  • Session-scoped Approval results are written to that Session; global approvals never fake a Session record.
  • Stopping a Session invalidates any pending approval.
  • BotRun cancellation, the tool journal, and UNKNOWN recovery all write to an independent durable event stream.

Known boundaries#

Browser checks a URL before the first navigation, and pins all of Chrome's HTTP, HTTPS, and WebSocket traffic to an in-process proxy with no direct fallback. The proxy resolves the domain separately for every navigation, redirect, iframe, subresource, and script request, rejects any restricted address, and connects only to the specific address it already checked. HTTPS keeps the original domain's TLS SNI. Requests fail closed on resolution, connection, idle, or traffic limits. The top-level URL re-check after every non-close action is an extra line of defense, not the network interception itself.

Remote MCP, Browser automation, and automatic knowledge consolidation remain experimental. They're off by default in your personal configuration, and a project's configuration can't turn them on for you. Even enabled, don't use them for unsupervised handling of highly sensitive data.

Context Engineering#

Context is the working set for the current model call — not long-term storage. kxen loads Session history, Rules, explicit attachments, Notes, Memory, and running state into context through different paths.

flowchart LR
  Session["Session history"] --> Select["Selection and limits"]
  Rules["Rules"] --> Select
  Attachments["Explicit attachments"] --> Select
  Notes["Notes"] --> Retrieve["Relevance retrieval"]
  Memory["Memory"] --> Retrieve
  References["References"] --> Demand["Read on demand"]
  Retrieve --> Select
  Demand --> Select
  Select --> Compact["Window threshold and compaction"]
  Compact --> Context["Model call context"]

Explicit attachments#

Composer's @ reference supports files and directories inside the Workspace. Images, dropped files, and large pasted content join through the attachment system. Web and Docs-type context come from their own tool entry points, not from @ file search.

Current limits:

  • A file over 16 KB only injects its first 1 KB, with a prompt to use read.
  • A file over 64 KB isn't injected directly.
  • Total attachment size per context is capped at 200 KB.
  • A directory listing shows at most 200 entries.
  • Public images are capped at 5 MB.

Paths are canonicalized first, then checked against the Workspace boundary, symlink escape, and Safety-protected paths. Files authorized through the native file picker can enter context, but still can't cross Safety.

Composer's suggested context#

Composer's proactive suggestions and the actual context sent with your message are two different paths. Local candidate ranking can use the full draft, recent Session text, attachment paths, ContextSources, recently touched files, Git status/diff, and a Workspace file summary — but a candidate only becomes a chip or text once you accept it. Suggestions never enter the next model request on their own.

The default path never touches the network. An untrusted Workspace never reads candidate file bodies; sensitive paths, .git, .kxen, dependency and build directories are excluded, and the walk index respects .gitignore and never follows symlinks. Embedding and LLM suggestions only run once you explicitly enable them in your personal configuration, and only ever process a local shortlist. Any file identity the LLM returns must pass a closed-set validation against the local candidates on the backend.

Project instructions#

AGENTS.md and Rules hold the constraints that should stay in effect continuously. References are only read when a task actually needs them, so you don't inject your whole knowledge base into every turn.

History compaction#

Model context uses the context window from the catalog, falling back to 200,000 tokens when none is found. Estimation divides character count by 4; each image counts as a fixed 1,000 tokens (base64 length doesn't convert reliably to tokens, so kxen deliberately overestimates rather than underestimates). Compaction triggers once usage crosses 80% of the window.

Compaction:

  1. Keeps the system message.
  2. Keeps the most recent messages.
  3. Distills older text and context into a working summary.
  4. Never rewrites the original Session JSONL.
  5. Saves a compaction checkpoint used to rebuild the model's view.

If summarization fails, kxen keeps the head and tail of the older segment rather than losing recent context. You can also trigger the same durable compaction immediately with /compact in Composer.

Knowledge and Memory#

Knowledge Library is the unified management surface. Notes and Memory enter context through relevance retrieval; References are read on demand. None of them load their full body unconditionally.

Visible failure#

Attachment read failures, URL fetch failures, and limit overruns must be visible to both you and the model at the same time. A silent context loss would let the model keep working from a false premise.

MRM: the Model Resource Manager#

MRM is the Model Resource Manager. Every provider call from the main Session, a Subagent, a Workflow agent, a Team member, Knowledge embedding, provider-native search, and cloud audio transcription must go through the MRM view of the owning Session's Workspace. Provider connection tests and role trial-dispatches use the same admission and durable usage chain.

flowchart LR
  Request["Role request"] --> Binding["Role binding"]
  Binding --> Candidates["Provider, model, and account candidates"]
  Candidates --> Resolve["Resolve actual route"]
  Resolve -->|"preferred unavailable"| Fallback["Fallback role"]
  Fallback --> Candidates
  Resolve --> Limits["Pre-call budget, circuit, RPM, concurrency admission"]
  Limits --> Dispatch["Start call"]
  Dispatch --> Record["Dispatch record"]

Resolution order#

  1. Read the role binding.
  2. If a fixed account is set, check only that account.
  3. Otherwise, build candidates from the default and named accounts.
  4. Skip any candidate with no credentials or that currently can't route.
  5. Fall through the fallback chain if the preferred role is unavailable.

An unconfigured Team observer falls back to the execution role. Role resolution and call admission are two separate steps — resolve returns today's candidates without reserving a slot; begin_call re-checks budget, circuit, RPM, and concurrency only when a request is actually about to go out, so a budget settlement or circuit change that happened while waiting can't be bypassed by a stale resolution.

Resource limits#

  • A global concurrency limit applies to every model call.
  • Provider concurrency is shared across every account under that provider.
  • RPM is tracked per provider and per account over a rolling 60-second window.
  • Global daily token and provider cost budgets are admission thresholds against settled usage — once reached, new calls are refused, but a single request or in-flight concurrent requests may still cross the threshold, so don't treat it as a hard billing cap.
  • Repeated failures open a Workspace-scoped provider circuit; after cooldown, only one half-open probe is allowed. A custom provider's circuit identity also includes its protocol and endpoint, so changing the endpoint doesn't inherit the old one's failure state.
  • A CallPermit converts to a RAII Slot right before network I/O and releases its concurrency slot on completion or on any early exit.

Hot-reloading configuration preserves in-flight counts, RPM windows, and circuit state for the same Workspace, provider, and endpoint. Lowering concurrency never cancels a call already in progress — new calls simply wait until actual usage drops below the new limit.

Each Workspace has its own configuration view and circuit state; actual in-flight counts, provider/account RPM windows, and routing history are shared within the process. Every admission check uses the requesting Session's Workspace policy against that shared usage, so a background Session's routing doesn't change just because you switched the foreground Workspace. Creating another Workspace can't reset shared concurrency or RPM, but it also can't let one Workspace's endpoint failure trip another Workspace's circuit.

Account rotation#

A failed call on one provider can switch to another available account. Refreshed credentials get written back to the shared Auth store, so later runs and agents pick up the new credentials too.

Observability#

MRM keeps the most recent 50 routing resolutions in memory — role, provider, model, account, fallback source, and time. They show you what actually got resolved; they clear on restart and aren't the same as provider call counts or billing records.

Current boundaries#

The current implementation uses fixed, configuration-driven concurrency limits — it doesn't adjust to real-time latency yet. Dynamic AIMD isn't implemented.

Explicitly picking a model for a Session skips role-default model selection, but never skips its Workspace's credential resolution, budget, circuit, RPM, or concurrency admission.

Voice's cloud audio transcription passes through the same MRM concurrency, RPM, budget, and circuit admission before using a protected network connector, and writes a durable provider attempt before uploading. Because these services usually bill by audio duration while the current ledger can only express tokens, the result settles as UNKNOWN. See Voice and Usage.

Updated

Was this page helpful?