Contributing to the runtime
memnox-runtime is Apache-2.0 and takes contributions. It is the piece that actually decides whether an AI action is permitted, so it is written to be read: zero-dependency core packages, no framework magic, and a decision path you can follow end to end in an afternoon.
Setup
Node 20 or newer.
$npm install$npm test$npm run test:coverage$npm run build$npm run typecheck$npm run format$npm run deadcodeBefore opening a pull request, the same four that CI runs:
npm run format && npm run typecheck && npm test && npm run deadcodeThe eleven ground rules
These are not style preferences. Each one exists because breaking it would weaken a guarantee the product makes.
- The decision path stays deterministic. No LLM calls, network requests, clocks read from inside evaluation, or randomness. Intelligence belongs in a separate optional layer that explains decisions and never makes them.
- Fail closed. When identity or state cannot be verified, block. Never guess in the agent's favour.
- No
any.unknownplus explicit narrowing. Public and private async methods declare their return types. - No magic values. Numbers and strings with meaning live in a
*.constants.tsor a module-levelconstabove the class. - Every catch logs or rethrows. A silent catch is acceptable only for an expected first-run condition, and must say so in a one-line comment.
- Comments are one line and explain WHY. If code needs a paragraph, restructure the code.
- Every behaviour change ships with a test, in
packages/<name>/test. - Audit everything. Any new path through the gateway appends exactly one audit event. Not zero, not two.
- Take your dependencies as arguments. Nothing reaches for
console, the clock, the network orprocess.*in the middle of its logic. - No dead code.
npm run deadcodefails CI on an unused export. - Gate, not reviewer. Memnox decides whether an action is permitted and states what a class of change must satisfy. It never generates code, never opines on code somebody already wrote, and never reviews a pull request.
Testing without processes or sockets
The two places that used to be untestable were untestable for the same reason: ambient IO. Both were fixed structurally, and both are the pattern to copy.
CLI commands take a CliContext carrying the output port and a client
factory, so the real command tree runs against a recording output and a stubbed
transport:
const runtime = new FakeRuntime().on('POST', '/v1/actions/check', decision);
const { out } = await runCli(['check', '--token', 't', '--action', 'x'], runtime);The MCP firewall splits routing (FirewallSession, over a FirewallChannel)
from process plumbing (McpFirewall, which owns the child process and stdio).
Tests drive the session directly, no spawn, no stdin.
The MCP server follows the same split: McpServer.handle() is message-in,
message-out and owns no sockets, so a test drives the real protocol.
If your change is hard to test, that is usually the design telling you it reached for something it should have been handed.
What a reviewer checks
The question
Determinism
Direction
Failure
Audit
Scope
Tests
Where to read first
Every package has its own README covering what it does, how it is laid out, and what to touch when extending it. Read that before the source.
For how the product behaves from the outside, which is what you are changing, read this documentation site, particularly How a decision is made.

