The first Cedar policy had been eleven lines. It governed one agent in one project. Navan had written it in twenty minutes and committed it with a note in his physical notebook about the future being surprisingly legible.
The hundredth Cedar policy was also eleven lines. But the hundred policies together were not eleven hundred lines. They were four hundred and twelve, because policy composition was an art, and Navan had been learning the art for four months.
The breakthrough came when he realized that policies were not programs. Programs were sequences of instructions that executed in order. Policies were declarations of intent that evaluated in parallel. You didn't write a policy the way you wrote a function. You wrote a policy the way you wrote a law—stating what was permitted and what was forbidden, and trusting the evaluation engine to apply those statements to every request.
The first lesson was that shared templates eliminated redundancy. Twenty agents needed permission to read files in their workspace. Instead of twenty individual permit rules, Navan wrote one template: permit(principal is Agent, action == Action::"file.read", resource in Resource::"{workspace}/*"). The template was instantiated for each agent with its specific workspace path. One template, twenty instances, twenty policies reduced to twenty-one lines.
The second lesson was that entity hierarchies eliminated complexity. Instead of enumerating every domain an agent could access, Navan created domain groups. DomainGroup::"package-registries" contained npm, PyPI, and crates.io. DomainGroup::"ai-providers" contained api.anthropic.com, api.openai.com, and generativelanguage.googleapis.com. A single permit rule against a domain group replaced three individual rules. The policy set shrank further.
The third lesson was the hardest: knowing when not to compose. Some policies needed to be explicit, even if they were redundant. The deny rule for /etc/passwd access could have been subsumed by the general deny-all for files outside the workspace. But Navan kept it as a separate, named policy: @id("deny-etc-passwd"). Because when the audit log showed that an agent was blocked from reading /etc/passwd, the policy ID told the reviewer exactly which rule applied and why. Clarity in the audit trail was worth a few extra lines in the policy set.
"It's like writing laws," Navan told Jay. "You want them general enough to cover the common cases and specific enough to be understood when they're enforced. Too general and nobody knows why they were blocked. Too specific and you drown in rules."
"The tension between abstraction and specificity," Jay said. "That's not unique to policy. That's all of software."
"That's all of everything," Navan replied.
Justin reviewed the policy set that Friday. Four hundred and twelve lines governing one hundred agents across fourteen projects. He read them the way he read code—slowly, thoroughly, looking for gaps in the logic, contradictions in the rules, permissions that were too broad or too narrow.
He found two issues. A template variable that wasn't scoped correctly. A domain group that included a deprecated endpoint. Small things. Edge cases. The kind of imperfections that existed in any system of four hundred lines, whether that system was code or policy or prose.
Navan fixed them both in ten minutes. The policy set was four hundred and eleven lines now. One line shorter. One step closer to the platonic ideal of governance: the minimum set of rules that produces the maximum clarity of intent.
He wrote in his notebook: Policy composition is an art, not a science. The craft is in knowing what to compose and what to leave explicit. The scale is in the templates. The clarity is in the names.
The three lessons—templates for redundancy, hierarchies for complexity, explicit rules for clarity—are a masterclass in policy design. This should be required reading for anyone writing Cedar policies at scale.