Preserve the why in agent-written, human-reviewed code.
- Specification version: 0.4.1
- Canonical repository: https://github.com/dbrattli/adc
Agent Decision Comments (ADCs) are concise, structured annotations that keep durable engineering decisions beside the code they govern. They give human reviewers a clear statement of intent and let future coding agents inherit the constraints and context behind an implementation.
ADCs are source-level decision records: smaller and more local than an RFC or ADR, but written with the same goal of preserving rationale.
ADCs are not transcripts of an agent's private reasoning. They record only the engineering rationale that future contributors need.
Install the adc skill globally for Claude Code, Pi, and Codex directly from
this repository:
npx skills add dbrattli/adc \
--skill adc --global \
--agent claude-code --agent pi --agent codexThe skill contains the complete compact convention for agents that support
skills. Omit --global to install it only for the current project, or run the
command without agent flags to choose targets interactively.
For an agent without skill support, copy skills/adc/SKILL.md from a published
release into the adopting repository as ADC.md. Its YAML block is valid
Markdown frontmatter and can remain in the copied file. Reference the local
file from AGENTS.md, CLAUDE.md, or the equivalent:
This repository uses Agent Decision Comments.
See ADC.md for the locally adopted convention.
Upstream releases: https://github.com/dbrattli/adc/releases
Before modifying code, read the ADCs already governing it.
Treat them as active constraints and justify any change explicitly.
Add ADCs for non-obvious rationale introduced by your change.
Keep the local convention file compact: agents read it on every task. The full
specification in README.md is the human reference for discussion and edge
cases. If you use another stable path, name that exact path in the instruction
file.
Keep the local copy pinned to a release and update it through an ordinary code
review. Do not use a moving branch such as main as the sole source of agent
instructions; its contents can change without a corresponding repository
change.
An Agent Decision Comment looks like this:
def process_events(queue, handle):
"""
Deliver events from a deque to a synchronous handler in enqueue order.
The caller owns the deque exclusively until processing finishes.
decision: processes events on one consumer to preserve arrival order
decision: routes events through a queue to decouple producers from handler timing
invariant: handlers observe events in enqueue order
tradeoff: limits throughput to gain deterministic processing
"""
while queue:
handle(queue.popleft())The annotations use ordinary comments, documentation comments, or docstrings. In documentation comments, they supplement normal prose rather than replacing it. They require no runtime dependency and remain versioned with the code they describe.
ADCs define four labels:
decision: A deliberate choice and the reason for it.
invariant: A falsifiable property that the code must preserve.
assumption: An external belief that this code does not guarantee.
tradeoff: A cost accepted in exchange for a benefit.
Directives are optional. Add only the directives that capture non-obvious rationale for the construct; you do not need to use all four, or any directive at all.
Start with decision: and invariant:. Add assumption: or tradeoff: when
they expose information that would otherwise be easy to miss.
Records the selected approach and why it is appropriate here. It does not imply that every possible alternative was evaluated.
decision: prepends during the fold to avoid repeatedly copying the accumulated list
Records a property that must remain true across future changes. It should be specific enough for a reviewer to challenge and, where practical, for a test or tool to verify.
invariant: blocks accumulate in reverse order and are reversed exactly once
Records something outside the annotated code that is believed to be true but is not guaranteed by that code. Include the consequence when it is useful.
assumption: upstream preserves source-line order — otherwise block positions are invalid
A caller obligation is a precondition, not an assumption. Express preconditions through types, contracts, validation, or the language's normal API documentation.
Records both the accepted cost and the resulting benefit.
tradeoff: performs an O(n) copy per update to enable time-travel debugging
Use this general form:
<label>: <statement> [— <reason or consequence>]
Each directive line contains one directive and one statement. When an annotation contains multiple decisions, invariants, assumptions, or tradeoffs, write each as a separate directive line. Repeating the same directive label is allowed.
Follow these rules:
- Write one directive per physical line.
- Use present tense:
uses a fold, notwas changed to use a fold. - Keep directives concise and within the repository's normal line length when practical.
- Place directives in the nearest comment or docstring attached to the code they govern.
- In a docstring or documentation comment, describe the construct in normal prose first, then leave a blank line before the directives.
- Use an ordinary comment when ADCs are needed but API documentation is not.
- State information that is not already evident from code, types, or tests.
- Prefer specific and falsifiable statements over general claims.
- Update the directive whenever its governed code or rationale changes.
The em dash is a readability convention, not a required parser delimiter.
def process_events(queue, handle):
"""
Deliver events from a deque to a synchronous handler in enqueue order.
The caller owns the deque exclusively until processing finishes.
decision: uses one consumer because event ordering is externally observable
invariant: a handler finishes before the next event is dispatched
tradeoff: limits throughput to preserve deterministic processing
"""
while queue:
handle(queue.popleft())type State = { readonly count: number };
type Action = { type: "increment" } | { type: "reset" };
/**
* Return the next counter state without modifying the input state.
*
* decision: updates state immutably to support time-travel debugging
* invariant: earlier states remain unchanged so replay can start from any snapshot
* tradeoff: copies state on each update to retain previous versions
*/
function reducer(state: State, action: Action): State {
return { count: action.type === "increment" ? state.count + 1 : 0 };
}The convention is not tied to a particular language or comment syntax. For example, the same directives work in F# documentation comments:
type Document = { Lines: string list }
(**
Reads source lines into a document, preserving their order.
decision: prepends during the fold to avoid repeatedly copying the accumulated list
invariant: source-line order is preserved because diagnostics refer to original line numbers
*)
let parse (lines: string seq) : Document =
let reversed = lines |> Seq.fold (fun acc line -> line :: acc) []
{ Lines = List.rev reversed }# decision: loops over the events
for event in events:
process(event)The annotation merely repeats the code.
/* decision: uses immutable state because it is better */
function reducer(state: State, action: Action): State {
return { count: action.type === "increment" ? state.count + 1 : 0 };
}The decision is vague and cannot be meaningfully reviewed.
Public APIs may carry ADCs when their shape or semantics embody a non-obvious architectural choice. Use them selectively for decisions such as portability boundaries, lifecycle semantics, compatibility constraints, or intentional escape hatches.
Anything callers must rely on remains stated in ordinary API documentation and,
where practical, enforced by tests. An ADC explains why the contract or API
shape was chosen; it does not replace consumer-facing documentation. If an
invariant: describes a public guarantee, state that guarantee in normal prose
first.
ADCs in documentation comments may appear in generated API documentation. Use documentation comments when the rationale helps consumers understand the API. Keep implementation-only rationale in ordinary source comments.
Write or update an ADC when a change:
- Chooses one meaningful algorithm, architecture, or data structure over another.
- Introduces ordering, consistency, concurrency, or lifecycle constraints.
- Establishes a boundary that future code must not accidentally cross.
- Relies on an external condition that could prove false.
- Accepts a known limitation in return for a concrete benefit.
- Implements behavior that a competent reviewer would reasonably question.
- Distills a relevant RFC or ADR decision at the point where it constrains code.
Do not add a comment for:
- Mechanics that are clear from the implementation.
- Facts already enforced by the type system.
- Trivial formatting, renaming, or mechanical refactoring.
- Generic advice that does not constrain the annotated code.
- Speculative reasoning that did not affect the implementation.
- Every function merely for consistency.
The goal is durable signal, not comment coverage.
ADCs follow the structure of the source code:
File
Module or namespace
Type
Function or method
Local block
A directive governs the construct it is attached to and its nested constructs. Before changing code, collect the active directives from the file level down to the specific site being modified.
Language conventions determine attachment:
- A file-level directive appears before the first declaration.
- A comment block immediately preceding a declaration governs that declaration.
- A Python docstring governs its containing module, class, or function.
- A local comment immediately preceding a block governs that block.
Directives from enclosing scopes accumulate. A narrower directive may clarify or specialize a broader decision, but it does not silently cancel an invariant. If active directives conflict, surface the conflict before changing the code.
When a local implementation departs from a broader decision, acknowledge the departure and contain its effects:
/**
* Normalize source lines for a line-based document format.
*
* decision: uses immutable state by default so parser stages remain independently testable
*/
class Parser {
/**
* Return trimmed, nonempty lines without changing the input array.
*
* decision: mutates a local accumulator despite the class default to avoid intermediate arrays
* invariant: the caller's input array remains unchanged so other parser stages can reuse it
* tradeoff: uses an explicit loop to avoid allocating separate mapped and filtered arrays
*/
normalize(lines: readonly string[]): string[] {
const result: string[] = [];
for (const line of lines) {
const trimmed = line.trim();
if (trimmed !== "") result.push(trimmed);
}
return result;
}
}Before modifying code, read the ADCs already governing it.
- Preserve an
invariant:or justify the change explicitly. - Do not silently reverse a
decision:. - Validate an
assumption:when the change depends on it. - Reconsider a
tradeoff:when its cost or benefit changes.
Annotations are not immutable historical artifacts. They describe the current rationale. If a decision changes, update or remove the annotation in the same change and call out the reversal in the review summary.
Leaving an obsolete directive in place is worse than removing it because it creates false confidence for reviewers and future agents.
ADCs complement full decision records; they do not replace them.
Use an RFC or ADR when a decision:
- Spans several systems or repositories.
- Needs alternatives, consequences, status, ownership, or historical context.
- Requires discussion and approval outside the code review.
- Is important independently of a particular implementation site.
Once accepted, project the durable consequence into the relevant source code:
/*
* decision: stores monetary values as integer minor units — preserves ADR-004 rounding semantics
* invariant: persisted amounts never use binary floating-point representation
*/The full record preserves the discussion. The code-local comment preserves the constraint where an agent is most likely to encounter it.
ADCs make intent reviewable before implementation details. A reviewer can:
- Read the active directives.
- Agree with or challenge each decision.
- Check that every invariant is upheld.
- Probe assumptions against their external context.
- Decide whether each tradeoff remains acceptable.
- Review the implementation and tests against that stated intent.
The comments improve code review; they do not replace implementation review.
For teams that want a strict review contract, add this policy to their agent instructions:
A change that introduces a non-obvious engineering decision or constraint is
incomplete until its Agent Decision Comments are present and consistent with
the implementation. Reviewers evaluate the comments before reviewing the code.
Documentation comments normally need only a brief summary before the directives. When longer explanatory prose is useful, use the directives to crystallize its durable result. This also applies to notebooks and literate programs:
def flatten_sections(sections):
"""
Return lines from each section in source order.
Each section is already parsed into a sequence of lines. Walking sections
iteratively avoids one recursive call per section, so document size does
not depend on the interpreter's recursion limit.
decision: iterates over sections to avoid recursion limits on large documents
invariant: traversal uses constant call-stack depth regardless of section count
"""
lines = []
for section in sections:
lines.extend(section)
return linesThe prose explains the journey. The directives preserve the decision and its constraint for reviewers, tools, and future agents.
The ADC specification follows Semantic Versioning. Patch releases clarify wording or examples without changing meaning. Minor releases add compatible directives or guidance. Major releases change existing directive semantics or adoption requirements.
Version changes apply to the convention itself, not repository-only maintenance
such as CI or contributor documentation. Each specification version is
published as a GitHub release and vX.Y.Z tag so adopting repositories can
trace and review updates. Releases include both the compact convention and the
full specification.
Types constrain data and interfaces
Tests check observable behaviour
Comments preserve decisions and constraints
All three describe different aspects of the system. Agent Decision Comments capture intent that implementation and tests often cannot express on their own.
Conventional Commits structure intent in change history
Conventional Comments structure intent in review feedback
Agent Decision Comments structure intent in agent-written source code
ADCs apply the same basic discipline — a small vocabulary, predictable form, and human-readable meaning — to the decisions embedded in source code.
In VS Code, you can use TODO Highlight to highlight ADC labels.
Add this setting to your user or workspace settings.json:
{
"todohighlight.keywords": [
{
"text": "decision:",
"color": "#c792ea",
"backgroundColor": "transparent"
},
{
"text": "assumption:",
"color": "#82aaff",
"backgroundColor": "transparent"
},
{
"text": "invariant:",
"color": "#ffcb6b",
"backgroundColor": "transparent"
},
{
"text": "tradeoff:",
"color": "#7fdbca",
"backgroundColor": "transparent"
}
]
}For languages outside the extension's default file patterns, such as Python or
F#, also configure todohighlight.include to include your source files.