skip to content
2026#typescript#agents#security

kCortex

One server between coding agents and everything they touch - memory, plan approval, skills, files, tool calls, remote shells, throwaway machines - and not one credential in an agent's hands.

The problem

I run coding agents on three machines through three harnesses, and for a while every capability they needed came from somewhere else: a memory plugin, a plan reviewer, SSH keys in a dotfile, API tokens in environment variables, test machines set up by hand. Together they were a pile of state (a junk drawer with root access) I could not audit, and the keys sat with agents that forget what they did an hour ago and report success with total confidence. "All tests pass." They did not.

kCortex replaced the pile, and it starts from distrust. Agents forget, so memory is a service with rules. Agents overreach, so plans wait for a human and tools carry policies. Agents are confidently wrong, so nothing they say counts as proof. And agents never hold a credential, because a key an agent never sees is a key it cannot leak (my transcripts are the receipts).

Architecture

kCortex is a TypeScript server on Bun that compiles to one binary with its web app inside. State lives in one PostgreSQL database with pgvector (a Postgres extension for storing and searching vectors); durable work sits in Postgres queues, and memory holds only caches the database can rebuild. Agents see a single MCP (a service that exposes tools to coding agents) endpoint, and a CLI installs hooks for Claude Code, Codex, and OpenCode.

Every subsystem hangs off that spine:

  • Memory. Hybrid search with recency decay. A prompt gets at most six recalled excerpts, labelled as evidence, not instructions - an agent that obeys its own old notes just repeats last week's mistake faster. A write against a stale revision is refused.
  • Plans. A decision applies to exactly one version, new content reopens review, and a stale base gets a 409. The MCP surface has no decide tool, so an agent cannot approve its own plan, however convincing it finds itself.
  • Gateway. Agents send TypeScript that runs in QuickJS (a small embeddable JavaScript engine) with no filesystem, network, or timers - 64 MB and 30 seconds by default. A padded cell with exactly one phone, and I decide who it can call. Integration calls run under per-tool policies (safe, confirm, blocked), and an approval binds to the exact call, its arguments, and a fingerprint of the tool definition. Anything drifts, the agent asks again.
  • Access. SSH and WinRM run through a broker. Secrets are write-only: no endpoint reads, exports, or leases them, and they never land in a command line or a file the agent can see. They go in and never come back out - not to the agent, not to the UI, not to me. Host keys are pinned on first contact, a changed key fails closed, and each SSH run gets a fresh key signed with a one-hour certificate.
  • Sandboxes. An agent that needs a machine gets a throwaway one, so the thing it wrecks is something nobody will miss: a Linux container, Linux VM, or Windows VM on Incus. Each becomes an Access target trusting exactly one certificate principal, behind a jump account that can only forward to SSH, so the agent drives it through the same broker and never holds a key. Sandboxes reach the internet but no private ranges, memory is never overcommitted, an interrupted setup never runs twice, and revoking an HTTPS preview ends it at once.
  • Vault. Where agents put files instead of pasting a 4,000-line log into chat: content-addressed storage, uploads through signed links that expire within an hour by default, text extraction for PDFs and OCR for images, and static HTML previews served from immutable snapshots, sandboxed away from the kCortex origin. Revoking a share ends it, cached copies included.
  • Skills. A registry of agent workflows whose published versions never change.

Failure states are part of the contract. A gateway call that times out ends indeterminate and is never retried automatically. A broker crash marks admitted runs as result unknown instead of starting them twice, and a detached job that vanishes becomes lost, never an invented exit code. indeterminate looks ugly on a dashboard and is correct (pretty is not a state), and I will take that trade every time.

Boundaries

kCortex limits what an agent can reach; it does not make the agent right. Smarter agents are someone else's job. A shorter leash is mine.

Custody is narrower than it sounds. Stored secrets are unreadable through any API, CLI, or UI, but an agent holding the manage scope is a trusted operator: it could point a target at a host it controls and capture a password there. Native approval mode does not prove a person was asked, argument masking is a heuristic, and sandbox containers share the host kernel. There is no MFA on access changes, no rotation for gateway secrets, and exactly one broker, which an operator recovers after a crash. kCortex is a private service. What it stores belongs to its owner and stays private unless someone shares it, and the state behind it is locked down. Sharing is something you do, not something that happens to you.