The Five Modes
Every structured decision in haft moves through five modes. Each mode has a clear goal, entry conditions, outputs, and legitimate reroutes back to earlier modes when reality doesn't match expectations. This is not a rigid pipeline — it's a directed graph with well-defined upstream arrows.
Understand → Explore → Choose → Execute → Verify
↑ ↑ ↑ |
| | └──────────────────┘
| └────────────────────────────┘
└─────────────────────────────────────────┘ Forward is the default. Reroutes happen when a later mode reveals that earlier framing was wrong — not because you feel like iterating.
Understand
Entry: /h-frame or /h-reason
Goal: Frame the problem before solving it.
Tool: haft_problem(action="frame")
Most engineering mistakes happen because the problem was never properly stated. "We need caching" is not a problem — it's a solution in search of a problem. The actual problem might be "API response time exceeds 500ms on the product listing page."
The agent creates a ProblemCard with:
- Signal — the anomalous observation (not the assumed cause)
- Constraints — hard limits that any solution must respect
- Optimization targets — what to improve (1-3 max)
- Observation indicators — what to monitor but NOT optimize (Anti-Goodhart)
- Acceptance criteria — how you'll know the problem is solved
- Blast radius — what systems/teams are affected
Language precision triggers
The Understand mode watches for ambiguous terms and forces you to unpack them before
proceeding. When you use words like service, process,
quality, or component, the agent asks: "what do you mean
by that, specifically?" These words mean different things to different people. A problem
framed with ambiguous terms produces ambiguous solutions.
Explore
Entry: /h-explore (or /h-char first for characterization)
Goal: Generate 2+ genuinely distinct variants — different in kind, not degree.
Tool: haft_solution(action="explore")
Characterization (/h-char)
Before generating options, define how you'll compare them. This prevents bias — you can't
cherry-pick criteria to favor the option you already like. Run /h-char to
define comparison dimensions with roles:
- constraint — hard limit, must satisfy (e.g., "latency < 100ms")
- target — what you're optimizing (e.g., "throughput")
- observation — watch but don't optimize. Anti-Goodhart: if you optimize throughput, you might silently kill reliability. Marking reliability as "observation" means you track it without letting it distort your optimization.
Characterization is done BEFORE seeing options. This is deliberate — setting the rules after seeing the candidates is how you get biased comparisons.
Variant generation
"Different" means different in kind, not degree:
- Bad: "Redis vs Memcached vs in-memory cache" — three variations of the same approach
- Good: "Cache layer vs CDN optimization vs query redesign" — three fundamentally different strategies
Each variant gets:
- Description — what this approach does
- Strengths — why it might work
- Weakest link (WLNK) — what will break first. Not generic "cons" — the single thing that bounds quality.
- Risks — what could go wrong
- Stepping stone — does this open future possibilities even if not optimal now?
Diversity check
The tool computes word overlap between variants. If two share more than 50% of their words, it warns: "do these differ in kind, not degree?" You can proceed anyway, but the warning is there to prevent degree-variation masquerading as real alternatives.
Choose
Entry: /h-compare
Goal: Fair comparison on declared dimensions, identify the Pareto front.
Tool: haft_solution(action="compare")
Probe-or-commit gate
Before running the comparison, the agent checks readiness:
- Dimension coverage — are all declared dimensions scored for all variants?
- Variant diversity — do variants actually differ?
- Specific investigation — could a targeted probe (benchmark, prototype, expert consult) change the ranking?
The gate returns one of four verdicts:
| Verdict | Meaning |
|---|---|
| commit | Proceed with comparison — evidence is sufficient |
| probe | Run a specific investigation first (returns what to probe) |
| widen | Variants are too similar — go back to Explore |
| reroute | Problem framing is wrong — go back to Understand |
Constraint-aware Pareto
Constraints eliminate variants BEFORE dominance computation. If a variant violates a hard constraint, it's out — no matter how good it scores on targets. Only surviving variants enter the Pareto front calculation.
The result is a set of non-dominated options where no variant is strictly worse than another on all dimensions. This is not "pick the best" — it's "here are the options that trade off differently, each sacrificing something distinct."
Parity enforcement
Same inputs, same scope, same budget for all variants — or the comparison is junk. The tool checks for parity violations and flags them before producing results.
Language precision triggers
Subjective dimensions get flagged. If a comparison dimension is "maintainability", "simplicity", or "scalability" without a concrete metric, the agent asks you to operationalize it: "what specific measurement would indicate maintainability?"
Execute
Entry: /h-decide
Goal: Record the decision as a contract with invariants.
Tool: haft_decision(action="decide")
Adversarial verification gate
Before recording, the agent challenges the decision:
- Tactical depth: one-line counter-argument — "strongest reason this is wrong"
- Standard/Deep depth: five adversarial probes —
- Strongest counter-argument against the selected variant
- What would make this decision wrong in 3 months?
- Is any evidence self-referential (agent's own reasoning as proof)?
- Does the comparison hold under different load/scale assumptions?
- What's the cheapest experiment that could refute this?
If the counter-argument survives scrutiny, the decision is recorded.
Decision record
The record includes:
- Problem frame — linked back to the ProblemCard
- Selected variant — which option and why
- Invariants — what must always be true
- DO/DON'T rules — explicit behavioral contract
- Rollback plan — how to reverse if it goes wrong
- Refresh triggers — conditions that should trigger re-evaluation
- Affected files — exact code locations
- Claims with
verify_afterdates — "latency stays under 100ms" verified in 2 weeks
After recording, the agent snapshots file hashes as the baseline for drift detection.
Verify
Entry: /h-verify
Goal: Check what's stale, measure outcomes, maintain decision health.
Tools: haft_refresh(...) + haft_decision(action="measure")
Discovery
/h-verify scan finds everything that needs attention:
- Stale artifacts — R_eff < 0.5 or expired evidence
- Code drift — files changed since baseline snapshot
- Pending claims —
verify_afterdates that have passed without measurement
Actions
| Action | What it does | When to use |
|---|---|---|
scan | Find all stale artifacts + code drift + pending claims | Routine health check |
measure | Record evidence for a claim or acceptance criteria | Verifying a decision's outcomes |
waive | Extend validity with justification | Decision still valid, just expired |
reopen | Start new problem cycle from old decision | Conditions changed, need to reconsider |
supersede | Replace with a different artifact | New decision replaces old one |
deprecate | Archive as no longer relevant | Decision obsolete, nothing replaces it |
Evidence freshness
Each evidence item has a valid_until date and a congruence level (CL).
As evidence expires, R_eff degrades. New measurements mark old evidence as superseded.
See Decision Lifecycle for the full model.
Reroutes
Reroutes are legitimate upstream movements — not iteration for iteration's sake. Each reroute has a specific trigger:
| From | To | Trigger |
|---|---|---|
| Choose | Understand | Comparison reveals bad framing — dimensions don't capture what actually matters |
| Explore | Understand | Exploration reveals the wrong problem — you're solving a symptom, not the cause |
| Execute | Choose | Implementation shows chosen option doesn't work — need to pick a different variant |
| Verify | Any | Evidence shows the decision needs reconsideration — measurements failed, context changed |
Reroutes are signals, not workflow buttons. If you find yourself rerouting frequently, the problem is upstream: either the framing is consistently weak, or the exploration isn't generating genuinely distinct variants.
The Note fast path
Not every decision needs five modes. /h-note captures micro-decisions that
don't warrant the full cycle — "we use RWMutex here because contention is under 0.1%"
is a valid engineering choice that should be recorded but doesn't need a Pareto front.
Notes are validated (rationale required, conflict check, overlap check), auto-expire
after 90 days, and are explicitly NOT architectural decisions. If a note keeps getting
referenced, it's time to promote it to a proper decision via /h-frame.
See Notes & Micro-decisions for details.
Mapping from v5 commands
| v5 command | v6 mode | v6 command |
|---|---|---|
/q-frame | Understand | /h-frame |
/q-char | Explore (characterization) | /h-char |
/q-explore | Explore | /h-explore |
/q-compare | Choose | /h-compare |
/q-decide | Execute | /h-decide |
/q-refresh | Verify | /h-verify |
Next
- Notes & micro-decisions — the fast path for small choices
- Decision lifecycle — staleness, evidence decay, drift detection
- Team sync — sharing decisions across the team via git