Example: a feature from a ticket
Works today — plain markdown plus your agent; no corpus tooling required.
One small feature — silent token refresh on 401 — carried through the whole loop:
Pull → Spec → Task → Run → Review → Close. Every artifact appears in full, in the exact
shape its kit template freezes (templates/).
This page is the basic workflow with real content in every slot.
Step 1 — Pull: capture the ticket
Work starts in a tracker, so the first move is a snapshot: paste the ticket verbatim into
intake/ — the spec interprets; the intake preserves what was actually asked, so anyone
can later check the interpretation against the original. (The optional corpus pull captures
this snapshot for you; by hand you copy-paste.)
intake/WEB-123.md
---
type: intake
source: WEB-123
url: https://tracker.example.com/WEB-123
captured: 2026-06-08
---
# Intake: Users get logged out mid-session
WEB-123 — Users get logged out mid-session
Reporter: Dana M. (Support) · Priority: High · Labels: auth, web
Support has 14 tickets this month from users who lose long forms when their
session dies. Access tokens expire after 15 minutes; the app bounces the user
to the login screen and the form state is gone.
What we want: when the access token expires, the app should refresh it
silently and carry on — the user should never notice. While we're at it, can
we also add a "remember me for 30 days" checkbox on the login page?
Comments:
- Priya (PM), Jun 2: If the refresh token itself is dead, send them to
/login — don't try anything clever, and definitely don't loop.
- Tomas (Eng), Jun 3: Watch out for a request retrying forever on a bad
token. Whatever we do must retry at most once per request.
Nothing is edited — even the "while we're at it" line stays. It matters in the next step.
Step 2 — Spec: interpret the ticket
The spec turns the ticket into requirements an agent can build against, each with a
Verify with: line — a runnable check is the most useful single line you can hand an
agent [ORACLESWE]. The "remember me" ask is deliberately
left out, and the drop is recorded where the ticket's author can find it.
specs/auth-refresh/spec.md
---
type: spec
id: SPEC-AUTH-REFRESH
title: Silent token refresh on 401
status: ready
owner: web-platform
sources:
- intake/WEB-123.md
---
# Silent token refresh on 401
## Intent
When an access token expires mid-session, the web client refreshes it
silently and replays the original request — a user with a live refresh token
never lands on the login screen. Raised by support volume in WEB-123.
## Non-goals
- Token lifetimes stay as they are (15-minute access tokens).
- No changes to the login page itself.
- Mobile clients are out of scope; this spec covers the web client only.
## Requirements
### AC-001 — Silent refresh and replay
When a request returns 401 and a refresh token is present, the web client
must call `refreshSession` exactly once, then replay the original request
with the new session. A single original request is never retried more than
once.
Verify with: `npx vitest run web/tests/auth-refresh-401.spec.ts`
### AC-002 — Expired refresh token ends the session
When the refresh token is expired, the web client must clear the local
session and redirect to `/login`. No retry, no loop.
Verify with: `npx vitest run web/tests/auth-refresh-expired.spec.ts`
## Open questions
- None. (Redirect vs. an inline re-auth modal was settled in the ticket
comments: redirect to `/login`.)
## Affected areas
- `web/src/http/client.ts`
- `web/src/auth/session.ts`
## Dropped from sources
- "Remember me for 30 days" checkbox (WEB-123) — a session-lifetime
feature, not part of silent refresh; split out as WEB-131.
Dropped from sources is where the honesty lives: the ticket asked, the spec declined, and the reason is on record instead of silently vanishing.
Step 3 — Task: bound the agent's work
The task packet gives the agent three boundaries: what to implement, what to leave alone, and how to verify. The agent instructions are the standard block from the template.
tasks/auth-refresh.md
---
type: task
id: TASK-AUTH-REFRESH
source:
- SPEC-AUTH-REFRESH
scope: [AC-001, AC-002]
status: ready
---
# Task: Implement silent token refresh
## Source
- Spec: `specs/auth-refresh/spec.md` (SPEC-AUTH-REFRESH)
## Scope
Implement or preserve:
- AC-001 — silent refresh and replay on 401, at most one retry per request
- AC-002 — expired refresh token clears the session, redirects to `/login`
## Do not change
- The login page (`web/src/pages/login/`)
- Token lifetimes or the auth server API
## Affected areas
- `web/src/http/client.ts`
- `web/src/auth/session.ts`
## Verify
- [ ] `npx vitest run web/tests/auth-refresh-401.spec.ts` (AC-001)
- [ ] `npx vitest run web/tests/auth-refresh-expired.spec.ts` (AC-002)
## Agent instructions
1. Read the source spec (and change plan, if any) first.
2. Stay inside this task's scope. If a requirement can't be met as written,
stop and say why instead of improvising.
3. Run every Verify item and paste the real output — a claim without output
counts as unverified.
4. Before finishing, re-read your own diff as a skeptic: what would a
reviewer flag?
5. Fill `## Run summary` below — changed files, one line per Verify command
citing its pasted output above, out-of-scope edits, blocked questions —
and drop anything durable in `## Findings`.
## Findings
<!-- Anything durable discovered during the task — moved to findings/ at Close. -->
Step 4 — Run: the agent works, then reports
Hand the packet to any coding agent — Running agents covers the
mechanics. The run ends with the agent filling the packet's own ## Run summary
section — and dropping the durable discovery in ## Findings:
## Findings
- Refresh responses can arrive concurrently from multiple tabs — the
single-flight guard is load-bearing; candidate for Close
(FINDING-REFRESH-SINGLE-FLIGHT).
## Run summary
The run summary for TASK-AUTH-REFRESH.
Changed files:
- web/src/http/client.ts — 401 interceptor: refresh once, replay once
- web/src/auth/session.ts — clearSession(); expired-token guard
- web/tests/auth-refresh-401.spec.ts — new (2 tests)
Commands run:
$ npx vitest run web/tests/auth-refresh-401.spec.ts
✓ web/tests/auth-refresh-401.spec.ts (2 tests) 384ms
✓ replays the original request with the new session after a 401
✓ calls refreshSession exactly once per original request
Test Files 1 passed (1)
Tests 2 passed (2)
AC-002: clearing the session and redirecting to /login works — verified
manually against the dev server with an expired refresh token.
Worth saving: when several requests are in flight on an expired token, each
one sees its own 401 and each calls refreshSession. I serialized refresh
behind a single in-flight promise — otherwise one expired token hits the
token endpoint N times.
One Verify item has pasted output; the other has a sentence. That difference is exactly what the next step exists to catch.
Step 5 — Review: build the packet, route the exceptions
The reviewer (or their agent) fills the packet from the run summary and the diff — the rules are in Reviewing agent output. "Verified manually" is a claim, not evidence, and an empty or claim-only Evidence cell means Unverified, never Pass — a checklist rule: nothing in this repo enforces it; the reviewer inspects it.
reviews/auth-refresh.md — as first written:
---
type: review
id: REVIEW-AUTH-REFRESH
task: TASK-AUTH-REFRESH
pr: https://github.com/acme/shop-web/pull/412
reviewer: mara@acme (the agent's session implemented; Mara reviews)
status: needs-human
---
# Review: Silent token refresh
## Summary
The 401 interceptor refreshes once and replays once, with test output
pasted. The expired-refresh-token path (AC-002) has no test output — only a
manual claim — so it stands Unverified. One finding candidate: concurrent
401s fanning out to multiple refresh calls.
## Changed files
- `web/src/http/client.ts`
- `web/src/auth/session.ts`
- `web/tests/auth-refresh-401.spec.ts`
## Requirement coverage
| ID | Result | Evidence | Human attention |
| ------ | ---------- | --------------------------------------------------------------------- | --------------- |
| AC-001 | Pass | `auth-refresh-401.spec.ts` — 2 tests passed, output pasted in PR #412 | no |
| AC-002 | Unverified | no test output — "verified manually" is a claim, not evidence | yes |
Spot-checked: AC-001 — re-ran `npx vitest run web/tests/auth-refresh-401.spec.ts` myself → 2 passed.
## Human attention
1. AC-002 is Unverified — the spec names `auth-refresh-expired.spec.ts`,
which was never written. Ask the agent for the test plus pasted output.
2. Finding candidate: concurrent 401s fan out to multiple refresh calls;
the single-flight guard in `client.ts` deserves a saved finding.
## Suggested decision
Block until AC-002 has real evidence.
The Unverified row is the packet doing its job: it turned a 3-file diff into one precise follow-up. The agent writes the missing test and pastes the run:
$ npx vitest run web/tests/auth-refresh-expired.spec.ts
✓ web/tests/auth-refresh-expired.spec.ts (2 tests) 291ms
✓ clears the local session when the refresh token is expired
✓ redirects to /login when the refresh token is expired
Test Files 1 passed (1)
Tests 2 passed (2)
The packet is updated in place — the row flips, the frontmatter status becomes pass,
and the decision changes:
| AC-002 | Pass | `auth-refresh-expired.spec.ts` — 2 tests passed, output pasted in PR #412 | no |
## Suggested decision
Merge.
Before signing off, the reviewer spot-checked one green row by re-running AC-001's command — the convention that keeps a tidy table from becoming a rubber stamp.
Step 6 — Close: save the finding, update the board
The "worth saving" note becomes a finding — a durable lesson with its evidence attached (Saving findings covers the lifecycle).
findings/refresh-single-flight.md
---
type: finding
id: FINDING-REFRESH-SINGLE-FLIGHT
status: candidate
from: REVIEW-AUTH-REFRESH
date: 2026-06-11
related: [SPEC-AUTH-REFRESH#AC-001]
---
# Finding: Concurrent 401s fan out to multiple refresh calls
## What we learned
When several requests are in flight as a token expires, each sees its own
401 and each calls `refreshSession` — one expired token becomes N refresh
calls and N replays unless refresh is serialized behind a single in-flight
promise.
## Evidence
PR #412 run summary and `reviews/auth-refresh.md`; the single-flight guard
in `web/src/http/client.ts`, with `auth-refresh-401.spec.ts` exercising the
exactly-once rule.
## Where it applies
- Any client where multiple requests can be in flight when a token expires.
## Where it does not apply
- Clients that serialize all authenticated requests through one queue.
## Future guidance
When touching a token-refresh path, check for a single-flight guard first —
and write the concurrent-401 test before the happy-path one.
Finally, the workboard rows. The closed task's link points at its review packet — the board's one honest rule is that a done claim links its evidence.
status.md (the rows this feature added)
| Item | Type | State | Link |
| ----------------------------- | ------- | --------- | ----------------------------------- |
| SPEC-AUTH-REFRESH | spec | done | `specs/auth-refresh/spec.md` |
| TASK-AUTH-REFRESH | task | closed | `reviews/auth-refresh.md` |
| FINDING-REFRESH-SINGLE-FLIGHT | finding | candidate | `findings/refresh-single-flight.md` |
## Human attention
- FINDING-REFRESH-SINGLE-FLIGHT pending acceptance.
That's the whole loop: a ticket became a snapshot, a spec with a recorded drop, a bounded task, a run with pasted evidence, a packet that blocked once and passed honestly, and a lesson that outlives the session.
Other examples
- A bug fix — the bug shape: spec check, a one-line amendment, and a regression test that runs red before the fix.
- A large PR review — the main demo: a change-plan-driven refactor and the packet that makes a 41-file agent PR reviewable.
Ready to run the loop on your own repo? Get started — copy the kit and write your first spec.