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.

← All posts