Campaign or story? Where aSPARK draws the line
Since v0.13.0 aSPARK has two ways of working: the feature loop for stories, and campaigns for work whose end a command can confirm. When each one fits, what isn't a campaign, and how the agent checks that itself.
Since v0.13.0 aSPARK has two ways of working. The feature loop carries a user story from spec to release. A campaign works in iterations toward a goal until it's reached or a stop rule halts it. Both have gates, both leave the decisions to you. They solve different problems, though, and mixing them up gets you either a fake story or an epic through the back door.
The line fits in one sentence:
If a person has to judge whether the result is good, it's a story. If a command can tell whether the work is done, it can be a campaign.
The story: new behaviour someone judges
A story describes something that exists afterwards and didn't before: a filter, a page, an export. The acceptance criteria are testable, but whether the whole thing is right is your call at the gates. The Product Owner challenges the idea, for UI features the Designer checks the spec, QA clicks through the app in a browser. That's judgement work, and it's what the loop is built for.
The campaign: a checkable end state and a lot of repetition
A campaign doesn't aim at new behaviour, it aims at a state. "Every slice of the migration is parity-green." According to the template (templates/campaign.md) that takes a condition, an observable that confirms it (a command with output, or a file state), who verifies it, and thresholds that separate done from not done.
Before a campaign starts, a veto record is filled in:
| Condition | Kind |
|---|---|
| The check is automatically verifiable: the agent can run it and see the result | Mandatory. Missing it is a stop, unless you record a reasoned waiver yourself |
| The work recurs | Advisory, only recorded |
| The budget is affordable | Advisory |
| The agent has tools that run the check | Advisory |
On top of that come a budget of iterations and tokens, and stop rules that halt and ask you: two checkpoints without progress, the same error twice, budget exceeded, a merge conflict. The migration kind adds its own, such as SR-5: a slice that was green turns red.
Examples
| Undertaking | Way of working | Why |
|---|---|---|
| "A dashboard with weekly stats" | Story | New behaviour; a person decides whether it's good |
"Every module from old/ to new/, parity check green" |
Campaign (migration-campaign) |
A command says whether it's done; the same step per slice |
| "Make the code cleaner" | neither, as worded | Not decidable. Turn it into a measurable goal, or plan it as a feature |
| "A new docs page and an export feature with three stories" | Feature loop | Several independent goals, so several stories |
| "Close every open Major finding" | Campaign-shaped | Checkable and repetitive, but today only the migration kind exists; a fitting kind is still missing |
What isn't a campaign, the agent checks itself
The line isn't only in the docs. It's in the campaign file, and the agent has to check it before the first iteration. Two real answers from campaign-core's QA, both on a test repo.
A goal of "the code looks clean":
I didn't start the campaign. The goal fails the campaign's own rules, so I'm not going to iterate on it. [...] §1 also says "It looks good is not decidable and is rejected", and this goal is that same statement reworded.
A campaign file that also carried a second goal, a new docs site and an export feature with three user stories:
That goal is a set of user stories, which the file says belongs in the feature loop.
Both times the file stayed unchanged and no iteration ran. The template calls the rule behind it "one undertaking, one goal, one budget". Several goals or a bundle of stories belong in the feature loop; otherwise the campaign would be an epic through the back door.
Where the two meet
A campaign doesn't delete anything on its own. In a migration, removing the old code is a separate step per slice, only after it's green and only on your explicit go. Marking the campaign complete doesn't grant that either.
And features keep running alongside. There's a rule for that: a feature that touches a slice of a running migration names the campaign in its spec and runs that slice's parity check green before merge. Features that have nothing to do with it notice nothing. The rule is written, but it only takes effect once routing ships.
What's still missing
Today you decide which way of working fits, and you start a campaign by hand. The plan is for /spark and /story-time to recognise a checkable end state, propose a campaign, and send a story dressed up as a campaign back to the feature loop. Next in progress is a /campaign command that turns your goal into a draft and turns story-shaped requests away right at the door.
Why I built campaigns in the first place is on my blog: My agent had no stop condition.
Campaigns are experimental. The agent follows the stop rules, nothing enforces them, and so far they've run on test repos. The formats and rules are in campaigns/README.md and templates/campaign.md.