Writing Outcomes
How to decompose any input into well-structured Outcomes. Reference: METHOD.md for full methodology.
What Is an Outcome
An Outcome is a result that delivers value. It is the fundamental unit of work in the Canopy Method.
An Outcome is NOT a task. It describes what is TRUE when the work is done, not what actions someone takes.
The Test
For every Outcome, ask three questions:
- Is it a result? Can you say "When this is done, [title] is true"?
- Is it atomic? Can one person own it and complete it without decomposing further?
- Is it testable? Can you write 2–5 success criteria that definitively prove it's done?
If any answer is no, rewrite or split.
Writing the Title
The title is a statement of the result. Use these patterns:
Pattern: "[Subject] can [action]"
- "Customers can book appointments online"
- "Team leads can see which Outcomes shipped this week"
- "Clients can approve deliverables through the Portal"
Pattern: "[System/Product] [does thing]"
- "Invoice emails send automatically on the 1st of each month"
- "Search results return in under 200ms"
- "Dashboard displays real-time booking status"
Pattern: "[State change]"
- "Payment processing migrated from Square to Stripe"
- "Legacy data imported and validated"
- "Brand guidelines applied across all public pages"
NEVER write titles as tasks:
- ❌ "Build booking API" → ✅ "Customers can book appointments online"
- ❌ "Implement search" → ✅ "Users can find content by keyword"
- ❌ "Fix auth bug" → ✅ "Users can log in reliably across all browsers"
- ❌ "Update database schema" → ✅ "System supports multi-location scheduling"
- ❌ "Research payment providers" → ✅ A Decision, not an Outcome
Writing Success Criteria
Each Outcome has 2–5 success criteria. These are testable conditions that prove the Outcome is complete.
Rules:
- Each criterion is independently verifiable (true or false)
- All criteria must be true for the Outcome to be Done
- Criteria should be specific enough that two people would agree on whether it's met
- Avoid vague language: "works well," "looks good," "is fast" are not testable
Examples:
Outcome: "Customers can book appointments online"
- Customer can browse available services with descriptions and pricing
- Customer can select an available time slot for their chosen service
- Customer can complete payment via credit card
- Customer receives a confirmation email within 60 seconds of booking
- Provider receives notification of new booking with customer details
Outcome: "Team leads can see which Outcomes shipped this week"
- Signal dashboard shows a list of Outcomes that moved to Ship stage in the current week
- Each entry shows the Outcome title, owner, and the date it shipped
- The count of shipped Outcomes is displayed prominently
- The view updates in real-time when an Outcome ships
Outcome: "Search results return in under 200ms"
- Full-text search across all indexed content returns results in < 200ms at p95
- Search handles 100 concurrent queries without degradation
- Results are ranked by relevance with the most relevant appearing first
Priority: Now / Next / Later
Three levels. No more.
| Priority | Meaning | When to Use |
|---|---|---|
| Now | Someone should be working on this today | Currently in Build or about to enter Build. Active work. |
| Next | Up next when current work completes | Ready to start. Context is clear. Decisions are made. Days away. |
| Later | Important but not urgent | Backlog. Will be important eventually. Weeks or months away. |
Priority is about sequence, not importance. A Later Outcome is not less important — it's just not next in line.
How many Outcomes should be "Now"?
Per person: 1–3. If one person has 5+ Outcomes at "Now," they're overloaded and nothing is actually getting full attention. Limit Now, finish things, then pull the next one.
Outcome.kind: Classifying What Kind of Work
Every Outcome has a kind — an enum that classifies what type of result this is. This affects how the Outcome renders in roadmap reports (especially customer-facing ones) and how Signal aggregates throughput.
| kind | Meaning | Default rendering in customer roadmap |
|---|---|---|
feature |
New user-facing capability | Shown |
infra |
Internal infrastructure (deploys, CI, tooling) | Hidden (toggle to reveal) |
compliance |
Regulatory or policy work (HIPAA, SOC 2, GDPR) | Shown with badge |
content |
Documentation, marketing copy, written assets | Shown |
research |
Spike, prototype, or learning effort with no shipped feature | Shown with badge |
refactor |
Code restructure with no behavior change | Hidden (toggle to reveal) |
performance |
Speed, latency, throughput improvements | Shown |
operations |
Ongoing operational work (incident response, capacity, monitoring) | Hidden (toggle to reveal) |
Default: feature.
Why the kind enum exists
The audit showed Howler's README documenting this exact taxonomy informally (bug | feature | chore | infra); Pocket using feat | fix | infra; TestKillerProject using Bug | Feature. Real-world data shows the kinds are being invented spontaneously across the portfolio — formalizing prevents drift.
Customer-facing reports need different rendering than internal boards. A roadmap shared with a non-technical stakeholder should not lead with "Refactor the auth module" — that's true work but not the right surface for them. Hiding infra / refactor / operations by default (with a toggle to reveal) keeps the customer view honest without losing internal visibility.
Relationship to other primitives
kind=operationsis the "continuous SRE" path (per D-10). No new primitive needed; reuse Outcome with this kind + Invariants for SLO commitments. See INVARIANTS.md.kind=complianceOutcomes often link to Invariants (HIPAA, SOC 2). The Outcome is the change that brings the system into compliance; the Invariant is the enforced standing rule.kind=infraOutcomes often link to LivingProcess records when the work modifies an always-on process. See LIVING-PROCESS.md.
Outcome.is_retroactive: Backfilled Work
is_retroactive: bool flags work that was logged AFTER it was already shipped. Default false.
Use this when:
- Backfilling an audit chain for SOC 2 / HIPAA compliance (work happened before tracker existed)
- Adopting Canopy mid-project and capturing past work for historical visibility
- Recording maintenance work that happened opportunistically without being planned
Example: Plain backfilled PLAIN-1..4 as retroactive IDs for pre-tracker commits. TestKillerProject (78% SOC 2 readiness) used is_retroactive: true on Outcomes that documented controls already in place.
Retroactive Outcomes are visible in throughput reports but are clearly badged. They do NOT count toward forward-looking throughput trends — they represent past completed work being logged for the record, not current capacity.
Outcome.blocked_on: Internal vs External
When an Outcome is blocked, blocked_on distinguishes WHY:
internal— blocked by something the Canopy team / your clan owns (a pending Decision, a missing Context entry, an unfilled Outcome dependency)external_party— blocked by something outside your control (a third-party vendor, another clan via CrossClanDependency, a regulatory body)
Default null (not blocked).
The distinction matters for Signal layer reporting: blocked_on: external_party Outcomes don't reflect on your team's throughput — they reflect on the external dependency. Mixing internal and external blockers in the same dashboard misattributes slowdown.
See CROSS-CLAN.md for the paired-lifecycle entity when "external_party" means "another clan."
Outcome.gate: Approval Gate Mechanism (Phase 17)
An Outcome may carry an approval gate — a configuration that pauses the Outcome in awaiting_approval between Review and Ship until a named approver acts.
Fields
| Field | Type | Description |
|---|---|---|
gate |
jsonb (nullable) | Full gate config. NULL = no gate. |
gate.required |
boolean | When true, the Outcome enters awaiting_approval on the Review → Ship transition. |
gate.approver |
user.id | The named approver (single approver per outcome in v2.0). |
gate.approved_at, gate.approved_by |
timestamp / user.id | Set when approver approves. |
gate.rejection_reason, gate.rejected_at, gate.rejected_by |
text (min 10 chars) / timestamp / user.id | Set on reject; outcome returns to Build. |
gate.override_used, gate.override_reason, gate.override_by, gate.override_at |
boolean / text / user.id / timestamp | Sticky once set. Records a Claude-Code-session override; the outcome ships without waiting for the approver. Every override is recorded in audit_logs. |
gate_required |
boolean (NOT NULL, default false) | Denormalized scalar mirror of gate.required. Powers cheap queue indexes. |
gate_approver_user_id |
text (nullable) | Denormalized mirror of gate.approver. Powers per-approver inbox queries. |
Lifecycle
- Set the gate. Workspace owner or admin calls
outcome.setGate({ id, gate: { required: true, approver: userId } }). - Outcome moves through Define → Build → Review normally. The gate is configured but dormant.
- Review → Ship attempt. The
moveStageendpoint detectsgate_required = trueand detours the transition toawaiting_approvalinstead ofship. A signal eventoutcome.entered_awaiting_approvalfires. - Approver acts. They open the Approval Queue (workspace-scoped surface) or the outcome detail and call
outcome.approveGate(id)oroutcome.rejectGate(id, reason).- Approve → stage = ship;
gate.approved_at/approved_byset. - Reject → stage = build;
gate.rejection_reason/rejected_at/rejected_byset.
- Approve → stage = ship;
- Override path (exception). A user in a Claude Code session may call
outcome.overrideGate(id, reason). The gate'soverride_usedflag becomes sticky-true; if the outcome is inawaiting_approval, it advances to ship immediately. The override is recorded inaudit_logsand surfaces permanently on the outcome detail as "Overridden by {user}".
Rules
- Reject reason is mandatory, minimum 10 characters. Empty rejection is a vocabulary failure.
- Override reason is mandatory, minimum 10 characters.
- Approve / reject auth: the named approver OR workspace owner/admin.
- Override auth: any workspace member (the UI visibility flag — env var or per-user localStorage toggle — is the practical guard; the
audit_logsentry is the accountability mechanism). setGateauth: workspace owner or admin only.- A gate is single-approver in v7.0. Multi-approver (any-of / all-of) is deferred.
Signal events
outcome.gate_set— gate configured or cleared (info)outcome.entered_awaiting_approval— stage entered the gate (info)outcome.approved— approver approved (info)outcome.rejected— approver rejected (warning level)outcome.gate_overridden— override used (warning level — exception path)
See FLOW-AND-SIGNAL.md for the corresponding flow stage spec and the Approval Gate Queue reporting surface.
See docs/methodology/validation/DECISIONS-RESOLVED.md#D-03 for the v2.0 audit decision that introduced this primitive.
Decomposition: When to Split
Split an Outcome into multiple Outcomes when:
- It requires more than one person's expertise to complete
- It has success criteria that could ship independently
- It will take more than ~2 weeks even with AI assistance
- Different parts have different priorities
Example: Splitting a large feature
Too big: "E-commerce functionality is complete"
Split into:
- "Customers can browse products with images and pricing" (Now)
- "Customers can add items to a cart and adjust quantities" (Now)
- "Customers can complete checkout with credit card payment" (Now)
- "Customers can view order history and track shipments" (Next)
- "Customers receive automated shipping notification emails" (Next)
- "Admin can manage product inventory and pricing" (Next)
Each is independently shippable, independently testable, and can be owned by one person.
NEVER create subtasks
The Canopy Method does not have subtasks. If an Outcome needs decomposition, it becomes multiple Outcomes at the same level. Hierarchy creates hiding places for scope creep and makes progress invisible.
Linking
Every Outcome should be linked to:
- Context entries that inform how to build it (business rules, constraints, design principles)
- Decisions that must be made or were made during its lifecycle
- Other Outcomes only via shared Context or Decisions (no explicit dependency chains — let Context and Decisions reveal the relationships)
- Initiatives (optional) — if this Outcome is part of a cross-cutting subsystem (telephony bridge, plugin system, analyzer-pass program), link to its Initiative. See INITIATIVES.md.
- Invariants (optional) — if this Outcome must satisfy a standing constraint (HIPAA, brand-voice, audit-trail), link to the relevant Invariant. See INVARIANTS.md.
- Issues (optional) — if this Outcome is the fix for a specific bug, link via
linked_outcome_idon the Issue side. See BUGS.md. - LivingProcess (optional) — if this Outcome modifies an always-on process (Cortex Nexus config, Jungle Webmaster deploy), link to it. See LIVING-PROCESS.md.
- CrossClanDependency (optional) — if this Outcome depends on or fulfills cross-clan work, link via
linked_outcome_id_requestingorlinked_outcome_id_target. See CROSS-CLAN.md.
Common Mistakes
| Mistake | Fix |
|---|
| Title is a task, not a result | Rewrite: "When this is done, what is true?" |
| Success criteria are vague | Make each criterion pass/fail testable | | Outcome is too big | Split into 2–5 independent Outcomes | | Multiple owners assigned | Pick one. Who is accountable if it doesn't ship? That person. | | Outcome has subtasks | Convert subtasks to independent Outcomes | | Priority has more than 3 levels | Collapse to Now / Next / Later | | No success criteria | Always write 2–5 criteria before starting Build | | No linked Context | Before starting Build, identify what Context the builder needs |
Cross-refs
- METHOD.md — Outcomes are one of the 9 method primitives
- FLOW-AND-SIGNAL.md — Outcomes move through Flow stages
- DECISIONS.md — Decisions block Outcomes
- CONTEXT.md — Context informs Outcomes
- INVARIANTS.md — standing constraints Outcomes must satisfy
- INITIATIVES.md — cross-cutting groupings of related Outcomes
- CROSS-CLAN.md — cross-clan work as paired-lifecycle entities
- BUGS.md — bugs as Issues; Outcomes fix Issues
- LIVING-PROCESS.md — always-on processes Outcomes may modify
- VOCABULARY.md — preferred terms
docs/methodology/validation/DECISIONS-RESOLVED.md—D-05(kind),D-09(is_retroactive),OCF-03(blocked_on)