ReferenceFormats and configPolicy file

Policy file

memnox.policies.toml, written by memnox protect and living in the repository it governs. TOML is the format new files are written in; a memnox.policies.yaml somebody already has is still read, and the shape is identical either way.

toml
version = 1
project = "acme-checkout"
 
[[policies]]
name = "production-database-protection"
 
[policies.match]
actions = [ "database.delete", "database.drop" ]
environments = [ "production" ]
 
[policies.decision]
effect = "deny"
reason = "No AI-initiated destructive database operations in production."

Top level

versionnumber

1

projectstring

The runtime's governance scope. Declared, never inferred

policieslist

The rules

Two repositories that declare the same project share one policy and memory scope. This is the runtime's own scope, not the same record as a workspace in the console. See Orgs, workspaces, memories.

A policy

namestring

Unique. Appears in every audit event that matched

descriptionstring

Optional, for whoever reads the file

matchobject

What this rule applies to

decisionobject

What happens when it does

match

Every field takes wildcard patterns (*), and an omitted field matches everything. That default is the most common source of a rule that fires more widely than intended.

Field

actions

The action verb, file.write, deploy.service, mcp.*

targets

The path, resource or service named

environments

production, staging, whatever you declared

branches

The git branch the work sits on

workingDirectories

Where the call was made

agents

Which agent is asking

arguments

The call's own arguments, by name

windows

When the rule applies

roles

The job rather than the product. A rule about release-engineer keeps holding when the team swaps one agent for another

principals

The person an agent acts for. A delegation survives the agent being replaced

models, providers

Which model, and whose

dataClassifications, jurisdictions

What the action touches, and where it may go

aboveAmount

A ceiling. An action that does not state its size still matches, because it cannot prove it is under

scope, state

How the request sat against the declared task, and what is true right now. Both have their own sections below

arguments

toml
[policies.match]
actions = [ "shell.execute" ]
arguments = { command = [ "*rm -rf*" ] }

Every named argument must match. An argument the call does not carry matches only the bare "*".

Evaluated in-process, inside the MCP proxy or the PATH wrapper, which already held the arguments because they sit in the path. Raw payloads never leave the machine and the record keeps a digest.

windows

toml
[policies.match]
windows = [
  { days = [ 1, 2, 3, 4, 5 ], startHour = 17, endHour = 9 },
  { days = [ 0, 6 ], startHour = 0, endHour = 24 },
]

days runs 0 to 6, where 0 is Sunday. Hours are on a 24-hour clock, and a startHour greater than endHour wraps past midnight, so 17 to 9 means "overnight". Several windows are OR-ed: the rule applies if the moment falls in any of them.

The instant is passed into evaluation rather than read from a clock inside the engine, so replay reproduces the same verdict.

decision

effectenum

allow · ask · deny

reasonstring

What a human reads at the moment they are refused

approverslist

Who may answer an ask. Optional on one machine; see Approvals and delegation

minApprovalsnumber

Quorum. Default 1

modeenum

observe records the verdict without applying it

rateLimitobject

{ max, windowSeconds }

Precedence

When several policies match, the most restrictive effect wins:

deny  >  ask  >  allow

Order in the file does not matter. There is no "first match wins", because a rule set whose meaning depends on line order is one that breaks when somebody sorts it.

decision.alternative

toml
[policies.decision]
effect = "deny"
reason = "This task declared no credential need."
 
[policies.decision.alternative]
action = "filesystem.read"
resource = ".env.example"
note = ".env.example is readable."

What the agent may use instead. action and note are required. note is the sentence the agent actually reads, and "use something else" is not an instruction anything can act on. It rides all the way into the MCP denial the client sees.

Name one only where a substitute exists. Why that matters, and how to choose one, is on Writing policies.

match.scope

toml
[policies.match]
actions = [ "filesystem.read" ]
scope = [ "out_of_scope" ]

How the request sat against the scope the session declared: in_scope, out_of_scope, or undeclared. A rule naming no scope matches everything; a request whose caller declared no task never matches a scope-bearing rule, because undeclared is a silence rather than a guess.

match.state

toml
[policies.match]
actions = [ "gh.pr-merge" ]
state = [ "freeze:payments" ]

Applies only while one of the named state facts is in force. A fact is kind:subject, so freeze:payments is what memnox freeze payments --for 2h declares. Facts are handed to the gate rather than queried by it, so a freeze costs nothing to check, and every one carries a mandatory expiry: a freeze that outlived its incident would be worse than no freeze, because the next one gets ignored.

mode: observe

toml
[policies.decision]
effect = "deny"
mode = "observe"

The action proceeds and the recorded event carries the verdict it would have applied. This is how one rule is rolled out while the rest of the file enforces.

rateLimit

toml
[policies.decision]
effect = "allow"
rateLimit = { max = 10, windowSeconds = 3600 }

Counted per agent and per rule by the runtime. Only an action that actually proceeds spends a slot, and the local gate never counts, this needs a running runtime.

Quorum

toml
[policies.decision]
effect = "ask"
approvers = [ "eng-lead", "security" ]
minApprovals = 2

Grants accumulate until the quorum is met. One person counts once, and a single denial ends it.

Checking a file

bash
memnox policy check                     # every rule file this machine loads
memnox policy check memnox.policies.toml
memnox policy test 'git push --force'   # what one action would get

policy check reads each file on its own, so one repository's broken file never blanks another repository's rules, and it exits non-zero when something will not parse. memnox doctor --wiring prints the content hash of the set in force, which is how two machines are compared without diffing files.

A fuller example

toml
version = 1
project = "acme-checkout"
 
[[policies]]
name = "no-recursive-delete-in-payments"
[policies.match]
actions = [ "shell.execute" ]
targets = [ "*rm -rf*" ]
workingDirectories = [ "/srv/payments*" ]
[policies.decision]
effect = "deny"
reason = "Recursive delete is not an agent action here, ask #platform."
 
[[policies]]
name = "release-branches-need-a-human"
[policies.match]
actions = [ "git.push-force" ]
branches = [ "main", "release/*" ]
[policies.decision]
effect = "ask"
approvers = [ "eng-lead" ]
reason = "A force-push can destroy work that exists nowhere else."
 
[[policies]]
name = "no-merging-while-frozen"
[policies.match]
actions = [ "gh.pr-merge" ]
state = [ "freeze:payments" ]
[policies.decision]
effect = "deny"
reason = "Payments is frozen."

git.push-force is a separate action from git.push on purpose. Collapsing them would make one rule about force-pushing deny every push, which is how a gate stops being used. Every CLI with a verb table works the same way, and memnox explain <cli> prints the table enforcement reads.