Founders

How to Write a Decision Rule an Agent Can Apply

Antonio Evans9 min read

A split scene: on the left, a printed decision log with columns for context, options, chosen action, and reason; on the right, the same logic expressed as a simple if-then rule block.
Key points

Key points

  1. 1A decision rule is a small ordered set of conditions built from fields your decision log already contains, each paired with the exact outcome a human chose at the time.
  2. 2Every rule needs a replay test that runs the rule against old log rows and compares its output to the recorded decision, so drift shows up as a failing test rather than a surprise in production.
  3. 3Conditions must come from inputs the agent can actually observe at run time, and actions must come from a closed list of outcomes, never free text.
  4. 4When the rule and the log disagree, the default fix is to add the missing condition or the missing field, not to loosen the rule until it passes.

Start from the log, not from the policy

Most teams write an agent rule the same way they write a policy document: a paragraph of intent that a human reads and interprets. An agent cannot apply a paragraph reliably, because the same sentence supports several different orderings and thresholds. The decision log solves this because it already records what a person did with a specific input.

A decision log is a structured record of what was chosen, why, who was involved, what alternatives were considered and what outcome was expected, which makes it the closest thing to a labeled dataset that most operational teams already own according to the monday decision log guide. That framing matters: if your log captures the choice and the reasoning, the rule is a compression of that table, and the test suite is a replay of it.

The field list is the part to get right first. A workable log entry separates the decision from its context so the decision can be quoted on its own, and it adds an owner, a date, an outcome status and a revisit trigger as the Boardmix template sets out. The revisit trigger is what later becomes your drift alarm. Two fields in a typical template map directly onto the two halves of a rule:

Log fieldRule componentExample
Input fields (the situation)Conditioncustomer tier, error code, order value, plan type
DecisionActionrefund, escalate, approve, request more evidence
Owner and dateRule version metadatawho approved this rule, when
EvidenceLink attached to the test caseticket, metric, incident record
Revisit triggerDrift check"reopen if refunds over 500 exceed 5 percent of volume"
Outcome (adopted, deferred, rejected)Test label sourceadopted rows are labels, rejected rows are counterexamples

If your log has no separate input fields, you are not ready to write a rule. Add them first. This is the single most common failure point, and it is cheap to fix: for the next twenty decisions, record the four or five facts the decider actually looked at.

Extract the decision fields

You cannot build conditions from prose. Pull the log into a table where each row is one decision and each column is one field. Do it in three passes.

Pass one: name the inputs. Read ten rows and list every fact a decider used. Expect four to eight distinct fields. If you find fifteen, you are logging too much; keep the ones that changed the outcome at least once.

Pass two: normalize the values. Free text kills rules. Map "Gold", "gold tier", "premium" to one value. Map error codes to a fixed enumeration. Anything that stays unique per row (a customer name, a free-text note) cannot be a condition and should be dropped from the rule.

Pass three: label the action. Convert each logged decision to one action from a closed set. If you find yourself wanting an action like "handle appropriately", the log row is not a decision yet.

A worked example from a support queue, where the log has forty rows and the actions are refund, escalate and deny:

condition order_value_usd > 500        -> action escalate
condition error_code in [E12, E19]     -> action refund
condition plan == "enterprise"         -> action escalate
condition retry_count >= 3             -> action refund
default                                -> action deny

Four conditions and a default cover a surprising share of operational queues. The default is not laziness; it is the honest statement that the rule does not know, and it should route to a named human rather than to a guess.

Policy engines show why the separation matters. Open Policy Agent's decision logs record the policy path queried, the input to the query, the result and a decision identifier for traceability per the OPA decision log documentation. That is exactly the shape you want: input in, result out, an identifier you can replay. A rule with the same shape can be tested the same way.

One ordering insight the log gives you that no policy document does: which condition wins when two match. Sort conditions by how often they appear in the log and how costly a wrong action is, put the expensive ones first, and write the order down. Ordering ambiguity is the second most common cause of an agent applying a rule "correctly" to the wrong outcome.

Turn conditions into actions the agent can execute

A condition is executable when an agent can evaluate it from data it has at run time, with no judgement. Three tests for each condition:

  1. Can the agent read this value from a system of record? If it comes from a human's memory at decision time, it belongs in the escalation path, not the rule.
  2. Is the comparison deterministic? "Order value over 500" is. "Customer seems frustrated" is not, and it belongs in a separate classifier with its own tests.
  3. Is the field populated on most rows? A condition that is null half the time produces silent defaults.

Actions need the same treatment. An action should name a tool call, a queue or a status change that the agent can perform, plus what it must record. Requiring the agent to write the matched condition and the log row it was derived from turns each production decision into a new test case.

LLM agents need the rule in their own channel. Addy Osmani describes keeping a project-local file such as ai_decisions.log and instructing the agent explicitly to log what, why and how, with regular human verification of the log in his automated decision log write-up. The same pattern works for rules: a rules.md in the repository, with the conditions in a fixed format the agent can quote back when it acts.

There is a reason to keep the rule text short and literal rather than paraphrased by a model. Language models trained on "A is B" do not reliably infer "B is A", a failure documented across model families as the Reversal Curse, with GPT-4 answering one direction of a relation far more often than the reverse in the paper's real-world tests reported in the Reversal Curse paper. A rule phrased as "low margin plus high volume means escalate" may not fire when the agent sees high volume plus low margin. Write conditions in one fixed order and test that order.

RULE refund_v3
Order:
  1. order_value_usd > 500            -> escalate
  2. error_code in {E12, E19}         -> refund
  3. plan == "enterprise"             -> escalate
  4. retry_count >= 3                 -> refund
  otherwise                           -> deny
Record: matched_rule_id, input_row_id, log_row_id

Write replay tests against the logged decisions

The test is the point. A rule without a replay test is a guess with better formatting.

The mechanism is simple: take every log row, feed its input fields to the rule, compare the rule's action to the logged action. The output is a table, not a pass or fail, because you need to see which rows disagree and why.

Log rowInput fieldsLogged actionRule actionMatch
014value 320, E12, basic, retries 0refundrefundyes
021value 780, E03, basic, retries 1escalateescalateyes
033value 210, E03, pro, retries 2denydenyyes
038value 610, E12, pro, retries 0refundescalateno
040value 95, E19, basic, retries 0escalaterefundno

Two disagreements out of forty is a 95 percent match, which sounds fine and is not. Row 038 shows a conflict in the log itself: a 610 dollar refund overrode the value threshold. The rule order puts value first, the human put cause first. Row 040 shows a logged escalation for a small amount on E19, where the rule refunds. Neither is a bug in the rule; both are gaps in the log or in the rule's ordering.

Set a threshold before you look at the results, and hold two of them. A match rate on seen rows tells you the rule fits history. A match rate on held-out rows (the last ten decisions, or a random fifth) tells you it generalizes. A rule that scores high on the first and low on the second has memorized the log, usually by overfitting a long chain of conditions to one unusual row.

Braintrust's guidance on testing agent systems makes the same structural point one level up: control the starting state, define a verifiable final state, and score requirements separately, so a polished output cannot hide a skipped action in their multi-agent testing guide. Apply that to a single rule by scoring the action, the matched condition and the recorded log row as three separate checks.

Log analysis is not optional at this stage. Final outcomes alone hide how the agent got there, and the paper on evaluable agent behavior argues that systematic tracking of inputs, execution and outputs is necessary because outcome-only scoring cannot tell a correct answer from a shortcut as set out in the log analysis paper. Your replay suite is log analysis applied to your own decisions.

Handle drift when the rule and the log disagree

Disagreement is the normal state, not the exception. Sort every mismatch into one of four buckets, because each has a different fix.

Bucket 1: missing condition. The human used a fact the rule does not read (a contract clause, a prior incident). Fix: add the field to the log going forward, then add the condition. Do not add the condition from one row; wait for a second independent row or a documented rule change.

Bucket 2: wrong ordering. Both conditions match, the human applied the other one. Fix: reorder and re-run the full suite. Ordering changes are high risk because they alter many rows at once, so never reorder without a full replay.

Bucket 3: log error. The recorded decision does not match the evidence attached to it. Fix: correct the log, keep the original entry marked as superseded and link to the correction, which preserves the history rather than creating two current-looking versions as described in the decision log template guidance. This bucket is why evidence links matter.

Bucket 4: genuine change. The world moved: a policy changed, a threshold was revised. Fix: version the rule, keep the old one, and add a note tying the change to its revisit trigger.

Decision logs are described as living documents that need regular updating, archiving of outdated entries and tracking of outcomes in the monday guide. A weekly review is the natural home for the drift check, and the GTD-derived weekly review structure of getting clear, getting current and getting creative maps onto triaging mismatches, updating the rule and asking whether a new condition is needed as Todoist describes the practice. A 20-minute Friday review that compares what was planned against what happened and commits to one change per week is enough to keep a rule honest per the 100tasks founder review template.

One discipline from the Eisenhower framework transfers directly: separate the decision that must be made now from the one that can be scheduled. A mismatch that would cause a wrong refund today goes in the immediate bucket; a mismatch on a rare error code goes in the scheduled bucket with a revisit trigger as the Eisenhower matrix describes the four quadrants. Without that split, every mismatch looks equally urgent and the rule stops being maintained.

Two signals worth watching between reviews. First, the share of traffic hitting the default action. If it climbs, the rule is losing coverage. Second, the age of the oldest unreviewed mismatch. Growth in either one predicts the rule will quietly diverge from human practice.

Where rule writing ends and tooling begins

Once a rule is written and tested, the remaining work is plumbing: running the rule on every request, logging each match, replaying the suite in CI, and routing mismatches to a queue. Teams that stop here usually discover the rule works but no one can see it working.

Two light options at this stage. First, keep the rule, the test harness and the review queue in the same repository as the rest of the operational code, so a rule change goes through the same review as a code change. Second, if the rule sits in front of several model calls (a classifier for the soft judgements, an embedder for similarity, a text model for the reply), route them through one endpoint so the decision log captures every call in one format. Quanta Inference puts text, image, video, audio and embedding models behind a single OpenAI-compatible key with prepaid US-dollar pricing, which keeps the logging surface to one place at Quanta Inference.

If you would rather have the first version built with you than assemble it from parts, describing the log, the desired rule and the review queue to a specialist is a reasonable use of Quanta.

FAQ

FAQ

How many log rows do I need before writing a rule?

Enough to see each input field change an outcome at least twice, which in practice is often twenty to forty rows for a queue with a handful of fields. Fewer rows mean the rule is a guess and your replay test has no statistical weight. If you have ten rows, write the rule but label it provisional and route every match to human review until the log grows.

What match rate should a decision rule hit?

Set the target before you measure, and set two: one for the rows the rule was built from and one for held-out rows. A rule that matches most historical rows but fails the held-out set has memorized the log. Treat held-out disagreement as the number that matters, and treat any mismatch on a high-cost action as a blocker regardless of the overall rate.

Should the agent apply the rule or the model?

Apply the rule for anything with a closed condition and a countable action, and hand the judgement calls to a model with its own tests. The split is usually clear from the log: conditions with deterministic comparisons become rule steps, and conditions that depend on interpreting text become a classifier whose output feeds the rule as a field. This keeps the audit trail readable.

What do I do when two conditions match at once?

The ordering decides, so write it down and test it. Sort by frequency in the log and by the cost of a wrong action, put the expensive conditions first, and re-run the full replay suite on every reorder. If a reorder changes many rows, that is the signal to split the rule into two narrower rules rather than to pick a winner.

How often should the rule be retested?

Replay on every rule change, and on a fixed schedule against the newest log rows. The scheduled run is the drift check; the change-triggered run is the regression check. If either run produces a mismatch that changes a high-cost action, halt automated application of that branch and route it to a person until the rule is updated and re-tested.

Sources

Sources

  1. Decision log: template, examples, and best practices 2026
  2. Decision Log Template: Fields, Examples and Follow-up
  3. Decision Logs
  4. Automated Decision Logs in AI-Assisted Coding
  5. The Reversal Curse: LLMs trained on “A is B” fail to learn “ ...
  6. How to test multi-agent systems - Articles - Braintrust
  7. Log analysis is necessary for credible evaluation of AI agents
  8. The Weekly Review: A Productivity Ritual to Get More Done - Todoist
  9. Founder Weekly Review Template (With a Worked Example) - 100 Tasks
  10. How to Prioritize Tasks Effectively Using the Eisenhower Matrix
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles