The problem
A rule in a prompt is advice. The agent reads it, weighs it against everything else in its context, and usually follows it. “Usually” is not good enough for money. And even a rule written as code has a quieter problem: it decides on facts, and somebody has to supply those facts. If the agent supplies “the receipt is attached” and “the amount is 4,800”, the rule is only as honest as the agent. So a first policy has to answer two questions, not one. What is the rule? And who vouches for each fact the rule reads?The idea
In Parmana, a policy is a JSON file atpolicies/name/version/policy.json. It has a name, a version, and an ordered
list of rules. Each rule pairs a condition with an outcome: approve or reject, with a reason written for the person who
will read it. Rules run top to bottom and the first one that matches decides. If none matches, the request is refused.
A sketch of the finance lead’s rule, shortened:
policies/ folder, such as
customer-refund 1.2.0, and the guide below uses high-value-payment from tutorial 14.
How Parmana does it
Every fact must be accounted for. For each fact a rule reads, the policy must say how it is held to account, or the policy does not load:- A copy of a request field goes in
boundSignals.claimAmountmust equal the amount in the action that will actually run, or the request is refused before any rule is evaluated. - A person’s decision goes in
approvalSignals.financeApprovedcounts as true only with a valid signed approval from a trusted approver, for this action, this resource and this amount, used once. - Anything else goes in
unboundSignalReasons, with a reason. Nothing checks it. The reason is there so a reviewer sees the gap, and such a fact should only ever refuse.
is_true, either as its whole condition or directly inside its top level all. A nested any does not count, because
another branch could match. A policy that breaks this fails validation. This holds for every policy, reads included.
The agent’s facts can refuse; only a person can approve.
Types are declared. signalsSchema gives each signal a type: boolean, number or string. A request that sends the
amount as text is refused before any rule runs.
End with a reason. The engine refuses an unmatched request anyway, but an explicit last rule records why, in words
the agent and the auditor can act on.
Check before proposing. npx vitest run packages/policy/tests/unit/ReferencePolicies.test.ts loads every policy
file through the same validator the server uses. The usual failures are a fact with no binding or reason, and an
approve rule without an approval signal.
Then the file goes through maker checker. One person proposes it, a different credential approves it with a step up
signature, and only then is it the version in effect. Until it is approved it authorizes nothing in production.
What it does not do
- It does not make a fact true.
receiptAttachedabove is the agent’s word. Declaring a reason is honest, not safe. A fact can be asked of the system that owns it throughsignalSources, but no shipped policy does that yet, and no real business system is connected. - An approval covers the action, the resource and, where declared, the amount. It does not fix every other parameter.
- It does not judge whether your rule is wise. A limit of ₹25,000 that should have been ₹2,500 passes every check.
- Rule order is your job. First match wins. The validator warns about rules that can overlap, but it is advisory and says plainly when it cannot tell.
- It does not remove the person from small actions. Every approved action needs a signed approval, at any amount. There is no automatic path, by design.