A plan's work is a set of tasks. Each task is one unit of work, in one repository, for one kind of executor, and Spektacular can report where they stand and implement them one at a time.
The task format
Tasks sit under a plan’s milestones, in its ## Milestones & Tasks section.
Every task carries four structured lines under its heading.
#### - [ ] Task: Add the epic split command
**Id:** 7c1e4b0a-9d3f-4e2a-8b61-0f5d2c9a7e34
**Repo:** spektacular
**Depends on:**
- 0b9f6d2e-5a41-4c7b-9e08-3d1f7a6c2b95 — Add a plan task reader
**Execution:** agent
Two to four plain-language sentences on what the task does and why.
**Acceptance criteria**:
- [ ] Outcome statement
The heading’s checkbox is the task’s completion: [x] means the task is done.
A task with nothing to wait for says so explicitly:
**Depends on:** none
What each line means
Idrequired
An opaque identifier issued by Spektacular with spektacular plan task-id.
It is unique within the plan and never rewritten once written, so it stays
the same when tasks are reordered, added or retitled.
Reporequired
Exactly one registered repository name. Work that spans two repositories is
two tasks.
Depends onrequired
Either none, or one - <id> — <title> line per task that must be finished
first. Only the id is read; the title is there so a reader can tell which
task it is. Every dependency must be a task in the same plan, and
dependencies may not form a cycle. There is no implied ordering: tasks that
can run in parallel simply do not depend on each other.
Executionrequired
agent, or human — <reason> with a non-empty reason.
Acceptance criteriacheckboxes
- [ ] outcome statements, ticked by the implement workflow only when the
criterion passed verification.
Saving a plan that breaks a rule is refused, naming the task: a missing line,
two repositories or one that is not registered, a dependency on an id that is
not in the plan, a cycle, a duplicate id, or an executor other than agent or
human with a reason. The plan stays exactly as it was before the refused
save.
When a task needs a person
The planning agent decides each task’s executor against three criteria.
A task is human when completing it needs any of:
secrets or access an agent will not have (production credentials, cloud
consoles, signing keys);
action outside the repository (deploying, releasing, DNS, purchasing or
approving something);
physical work only a person can do (connecting, flashing or setting up
hardware).
Anything else is agent. A task that would need both is split: the person’s
part becomes its own human task that depends on the agent’s. When the plan
is walked through for sign-off, the agent names every human task and its
reason, so you know which work is waiting on a person.
A review is never a task. A design or UX review, a sign-off, or checking
behaviour by hand is something you look at rather than work that makes the
change exist, and a task for it would never be ticked. The plan lists each
review as a manual check instead, and implementation writes it into the
test plan with what to look at and what counts as passing.
Task identifiers
Plan authors, agents and people alike, ask Spektacular for each new task’s id
rather than inventing one:
Terminal window
$ spektacular plan task-id
{ "id": "7c1e4b0a-9d3f-4e2a-8b61-0f5d2c9a7e34" }
Where ids come from is a project setting, chosen like a storage backend. The
default, uuid, issues random UUIDs:
.spektacular/config.yaml
plan:
task_id:
provider: uuid
Consumers treat ids as opaque strings. Because dependencies refer to ids
rather than to positions in the plan, they stay correct however the plan is
edited.
Seeing where work stands: status
spektacular status reports a piece of work from the epic down to each
task. It reads the documents when you ask, so it always reflects their
current content, for draft and final plans alike.
Give it an epic, a spek or a plan; you always get the whole epic. With no
name, it reports the workflow in progress. By default the report is a
readable tree:
000061_epic-split in progress 2/6 tasks ← requested
depends on: 000060_epics-and-seeded-specs
Milestone 1
[x] Add the split detection partial spektacular agent
[x] Add the epic split command spektacular agent
depends on: Add the split detection partial
Milestone 2
[ ] Add the split step to the spec workflow spektacular agent
depends on: Add the epic split command
[ ] Check dependencies at implement start spektacular agent
[ ] Document epics on the website spektacular-website agent
depends on: Add the split step to the spec workflow
[ ] Publish the release notes spektacular human: needs access to the release account
depends on: Document epics on the website
000062_spec-seeding specified no plan
depends on: 000060_epics-and-seeded-specs
The spek you asked about is marked ← requested and expanded down to its
milestones and tasks; the other speks in the epic show one line each, with
their task counts (k/N tasks, or no plan) and what they depend on. A spek
that is not in an epic is reported on its own, without the epic header line.
With --format json the same report is a single document for tools such as
orchestrators. It always has the same shape, whatever you named: an epic
(null for a spek that is not in one) and a specs list (one entry for such
a spek), so callers never branch on what kind of name they passed.
The task lists are shortened here; the real report carries every task of
every plan.
With no name, status reports the workflow in progress, whichever kind it
is. The readable tree gains a leading line, and the JSON report fills in its
workflow block:
Terminal window
$ spektacular status
workflow in progress: implement 000061_epic-split, at step analyze
The epic and specs that follow are the same as in the report above, left
out here. status <name> carries the same workflow block when the workflow in
progress belongs to one of the speks or plans it reports, and null
otherwise. With no name and nothing in progress, the readable form prints
no workflow in progress and the JSON report is {"workflow": null}.
pretty and json are the supported formats. Any other format, or a name
that matches no epic, spek or plan, is refused with the CLI’s usual
structured JSON error, whichever format was requested.
Status fields
workflowobject or null
The workflow in progress, when it works on one of the reported speks or
plans: its kind (spec, plan or implement), name, current_step,
completed_steps and updated_at. null otherwise.
requestedstring
The name you asked about. A caller that wants only that spek picks the
entry in specs whose name, or whose plan’s name, matches it.
epicobject or null
The epic’s name, document_status, created_at and sources, plus a
roll-up that is worked out on every call and never written back:
progress counts speks implemented of the total and tasks complete of the
total across every plan, and done is true once every spek is implemented.
null for a spek that is not in an epic.
specsarray
Every spek in the epic, in the epic’s order, or the one spek when it is not
in an epic.
specs[].statestring
Where the spek stands, derived from its documents and never stored. The
first that applies wins:
missing: the epic names the spek but it cannot be read. It is reported
rather than failing, so one broken link does not hide the rest of the
epic.
stale: the spek changed after its plan was written, under
plan.strict_spec_changes.
specified: the spek is written and has no plan.
planned: a plan exists with none of its tasks complete.
in_progress: some of the plan’s tasks are complete.
implemented: every task of the plan is complete.
The readable tree shows in_progress as in progress. The implement
workflow’s dependency check classifies speks the same way, so the two
never disagree.
specs[].document_statusstring
The spek’s lifecycle status. Consumers decide from it whether the spek is
ready to act on.
specs[].current_stepstring
The live step when a spec workflow is working on this spek; otherwise
finished for a closed spek, stale for a stale one, and empty for one
still open with no workflow running.
specs[].depends_onarray
The speks in the same epic that this one depends on; [] when it has none.
specs[].blocked_byarray
The dependencies that are not yet implemented. specs[].ready is true
when this list is empty.
specs[].sourcesarray
The material the spek was started from: its own sources, then its epic’s.
specs[].planobject or null
The spek’s plan, which shares its name: name, document_status,
current_step, and for a plan with tasks, progress (tasks_completed
of tasks_total) and tasks. A plan without task structure reports its
lifecycle with progress and tasks absent. null when the spek has no
plan.
specs[].plan.tasksarray
Every task, in plan order. The tasks[] fields below describe each entry.
tasks[].idstring
The task’s identifier.
tasks[].titlestring
The task’s heading text.
tasks[].milestoneinteger
The number of the milestone the task sits under.
tasks[].repo.namestring
The repository’s registry name, linking back to the project’s configuration.
tasks[].repo.locationstring
The repository’s declared git source. Empty when none is declared; it is
never read from a checkout’s git remotes.
tasks[].depends_onarray
Ids of the tasks this one depends on; [] for none.
tasks[].execution.typestring
agent or human.
tasks[].execution.reasonstring
Why a person must carry the task out; set for human tasks.
tasks[].completedboolean
Whether the task heading’s checkbox is ticked.
tasks[].acceptance_criteriaobject
How many of the task’s criteria are ticked: met of total. Completion and
acceptance criteria are separate facts. A task can be completed with a
criterion left unmet (an accepted deviation, for example), and the report
keeps that visible rather than hiding it.
Implementing one task
Name a task when starting the implement workflow and the run builds that
task alone.
Through an agent, ask for the task by name (“implement the epic split command task
of 000061_epic-split”) and the implement skill looks up its id and starts
the run.
The run still reads the whole plan, its context and research documents and
every design document it references, because a fact the task depends on may be
written only there. What it implements, tests, verifies and ticks is limited
to the one task. While it runs, spektacular status reports it in the
workflow block, ahead of the same requested, epic and specs as any
other status report:
The implement workflow’s new and goto results give the plan’s address and
location:
{
"plan_name": "000061_epic-split",
"plan_document": "plan",
"plan_path": "plans/000061_epic-split/plan.md"
}
plan_name and plan_document are what spektacular plan file read takes,
and plan_path is the plan’s location relative to the folder holding
config.yaml, never an absolute host path. See Documents.
Each run adds its entry to the plan’s changelog. The feature-level wrap-up
(the test plan, the feature changelog and reconciling the spec) happens once,
in the run that completes the plan’s last open task. Without a task,
implement works through the whole plan as it always has.
A task that cannot start is refused before anything is created, with an error
saying what to do instead:
task_not_found: no task in the plan has that id.
task_completed: the task’s checkbox is already ticked.
task_dependencies_incomplete: a dependency is not complete yet; the error
lists it.
task_requires_human: the task’s executor is human; the error gives the
reason.
Plans written before tasks
Older plans describe their work as numbered phases (Phase 1.1,
Phase 1.2). They are not rewritten or migrated: they still implement as a
whole and still make their milestone commits. status counts their phases
(k/N phases) instead of tasks, and single-task runs explain what such a
plan is missing (for example, that it contains no task
ids) rather than refusing it for its age.
See where tasks fit
Tasks are authored in the Plan stage and carried out in the Implement stage.
See the whole pipeline.