Stories · page 1 of 5
The markdown story format: every frontmatter field, groups, progress notes and follow-ups.
A story is one markdown file with its facts in the frontmatter and its brief in the body. This
page is for anyone who keeps stories in a folder, in the repo or in a notes vault outside it. It
covers where the files live, every frontmatter field, the status values, groups, the progress
notes Foreman writes, the follow-up stories it creates and every option of the stories block.
Stories can also come from Jira or
Salesforce Cases.
Where the files live
The default story source is a folder of markdown files:
"stories": { "adapter": "markdown", "dir": "stories" }
stories.dir is the folder, relative to the project or absolute. foreman init write creates
it with one example story, unless you pass --no-example. Every .md file in it whose
frontmatter has an id: line is a story; a file without one is ignored. Files are read in name
order.
A full story file for the Acme project:
---
id: ACME-12
title: Add mean()
status: todo
depends_on: [ACME-11]
group: 2
group_name: Statistics
order: 2
---
# ACME-12 · Add mean()
## What
Add `mean(numbers)` to `sum.mjs`, returning the arithmetic mean, and export it.
## Done when
`node --test` passes, including a test that `mean([1, 2, 3])` is 2 and `mean([])` throws.
## Progress notes
_Newest first._
The body is the brief the agents read. The architect takes “Done when” as the check that says the story is finished, so write it as something a person could run. Everything else in the body is yours to shape.
A folder outside the repo, such as a notes vault, works the same way. Point dir at it, or use
dirEnv so each machine names its own vault (see the options below). A story in a folder outside
the project is edited in place; one inside the project is left alone until the story’s own
commit marks it done (see Progress notes).
Frontmatter fields
Values can be bare, ‘single quoted’ or “double quoted”; quotes are removed when read. The five
keys marked “renamable” can be given other names with stories.fields.
| Field | What it holds | Who writes it |
|---|---|---|
id |
The story’s id, such as ACME-12. It must match stories.idPattern. Required. |
You |
title |
The story’s title. | You |
status |
One of the status values. Start a new story at todo: a file with no status is listed but never picked. |
You, then Foreman |
depends_on |
The ids of stories that must be done first, in any form the id pattern can find: [ACME-10, ACME-11] or ACME-10, ACME-11. Renamable. |
You, and Foreman when you approve a proposal |
group |
The group the story belongs to, such as 2. Renamable. |
You |
group_name |
The group’s name, such as Statistics. Renamable. |
You |
order |
A number; lower is picked first. Missing or not a number counts as 0. Renamable. | You |
blocked_by |
Why the story is blocked: story ids that must land first, or a reason in words. Renamable. | Foreman, when a story is blocked or you take it over |
follow_up_of |
The id of the story this one follows up. | Foreman, on a story it creates |
started |
The date the story was first started, YYYY-MM-DD. |
Foreman, on foreman story start |
completed |
The date the story was marked done. | Foreman, on foreman story done or finalize |
Foreman sets or replaces one frontmatter line at a time and leaves every other line of the file
exactly as it was. A value that YAML would misread (one with : or #) is written in double
quotes.
Status values
| Status | Meaning |
|---|---|
todo |
Open and not started. Picked when its dependencies are done. |
in-progress |
A slot is building it. A story left in progress by a run that died is picked first. |
blocked |
Not buildable now. When blocked_by starts with story ids (“ACME-3, ACME-4 must land first”), the story is picked again once they are done. A reason in words waits for you. |
ready-to-promote |
Its release is parked and finishes later. See Parked releases. |
done |
Shipped, or marked done by you. Final. |
retired |
Dropped from the backlog. Final, and left out of every count. |
Only todo, in-progress and blocked stories can be picked, and never an owner task. The full
rules are on How Foreman picks. A done or retired
status in the file is the truth: in state mode it beats whatever Foreman recorded on the side.
foreman story start, block, done, todo and finalize change the status for you; the
agents never edit a story file by hand.
Groups and order
A group is a set of stories that belong together, such as a phase of a plan. group is its key
and group_name its name; order ranks stories within and across groups. The picker takes the
story with the lowest order first, then the lowest number at the end of the id, then the id.
Dependencies cross groups: a story waits for every id in depends_on, whatever group it is in.
In the agents’ prompts the group is called by stories.groupLabel, “Group” by default; a plan
that calls them phases sets "groupLabel": "Phase".
stories.groupSummary asks for a summary when a group finishes. It is a path relative to dir
with {group}, {groupName} and {groupLabel} filled in, such as
"summaries/{groupLabel} {group}.md". When the last live story of a group ships, the session
runs foreman story summary <id>, which says whether the summary is due: every story in the
group that is not retired is done, the file exists, and it does not yet say
“<label> <group> is finished”. When it is due, the session appends a section headed
## <groupLabel> <group> is finished with what the group delivered, one line per story, and
the date. Unset, no summaries are written.
stories.resourcesByGroup names an exclusive resource every story in some groups needs:
{ "schema": ["2", "3"] } means no two stories of groups 2 and 3 build at once. See
Exclusive resources.
Progress notes
Foreman records what happened to a story as dated lines under a heading, ## Progress notes by
default (stories.progressHeading changes it). Each line is - **2026-10-17** — text, newest
first. When the heading is followed by an italic hint line such as _Newest first._, new lines
go after the hint; with no heading, Foreman adds one at the end of the file.
The lines are “Started.”, “Blocked: <reason>”, “Failed: <reason>”, the note foreman story done
or finalize is given, and, with features.jiraComments on (the markdown source follows the
Jira switch), one line for each note Foreman keeps on a story: a question it asked you and your
answer, the architect’s design, each round of review, how a session stopped, a release that
stopped and a follow-up it created. Each is the note’s title and a short summary folded to one
line of at most 500 characters. A story source that holds comments gets the full text instead;
see The story’s own record.
Where the lines go depends on stories.track:
file: Foreman edits the story file in place, the frontmatter and the progress lines, and touches nothing else in it. This is the default for a folder outside the project.state: Foreman never edits the file while the story runs. Status,blocked_by, other fields and the notes go to an overlay,.foreman/stories/<id>.json, and the agents read a brief at.foreman/briefs/<id>.mdthat is the file with the overlay applied. When the story is committed,foreman story finalize <id>in the story’s worktree writesstatus: done,completedand every note from the overlay into that checkout’s copy of the file, so the story’s own pull request carries them and the base branch reads done exactly when it merges. Run twice, it adds no line the file already has. This is the default for a folder inside the project.
Follow-up stories
foreman learn turns the minor findings a shipped story left behind into a follow-up story. By
default it asks first (“Follow up on ACME-12: 2 minor finding(s)?”); with
features.autoCreateFollowUps on it creates the story at once and tells you, with an Undo. The
switches are on How Foreman learns.
A follow-up is a new file in the layout of the others:
---
id: F-1
title: Follow-ups from ACME-12
status: todo
group: 2
group_name: Statistics
order: 2
depends_on: []
follow_up_of: ACME-12
---
# F-1 · Follow-ups from ACME-12
## What
- <finding> (`<where>`): <observed> — fix: <fix>
## Done when
Each listed finding is fixed, with a test where it is a behaviour, and `npm test` passes.
## Progress notes
_Newest first._
The id comes from stories.newId (default F-{n}): {n} is one more than the highest number
already used by ids of that shape, and {group} is the origin’s group, for templates such as
P{group}-F{n}. The id must match idPattern. The group fields are copied from the origin, and
“Done when” names the checks that always run. The file is <id>.md, or <id> <title>.md when
the origin’s file is named that way.
In file mode the new file goes straight into dir. In state mode it waits in
.foreman/stories/new/, where it is listed and picked like any other story, and finalize in
its own worktree writes it into dir, so the follow-up’s pull request is what adds the file.
Once dir has a file with that id, the waiting copy is deleted.
Options: dir, idPattern, fields, track, newId
Every key of the stories block for the markdown source. An unknown key is a config error.
| Name | What it does | Default |
|---|---|---|
adapter |
"markdown". |
markdown |
dir |
The folder of story files, relative to the project or absolute. Required. | stories |
dirEnv |
The name of an environment variable that, when set and not empty, replaces dir, such as NOTES_VAULT_DIR for a vault that lives somewhere else on each machine. |
unset |
dirEnvSuffix |
A path joined to dirEnv’s value, such as Stories when the variable names the vault root. Needs dirEnv. |
unset |
idPattern |
What an id looks like, as a regular expression without ^ or $ (ids are found inside text, such as depends_on). |
[A-Z]+-\d+ |
fields |
Which frontmatter key holds each of group, groupName, order, dependsOn and blockedBy. |
group, group_name, order, depends_on, blocked_by |
ownerTask |
{ "id": <pattern>, "title": <pattern> }: a story whose id or title matches is yours to do and is never picked. {} turns detection off. |
title matches (owner task), ignoring case |
resourcesByGroup |
{ "<resource>": ["<group>", …] }: every story in those groups needs that exclusive resource. |
{} |
groupLabel |
What a group is called in the agents’ prompts. | Group |
groupSummary |
Where a finished group’s summary goes, relative to dir, with {group}, {groupName} and {groupLabel}. |
unset: no summaries |
progressHeading |
The heading the dated progress lines go under. | ## Progress notes |
track |
file edits the files in place; state keeps changes in .foreman/ and only the story’s own commit touches the file. |
state when dir is inside the project, else file |
newId |
The id template for follow-up stories, with {n} exactly once and {group} as wanted. |
F-{n} |
A vault on two machines:
"stories": {
"adapter": "markdown",
"dir": "/Users/acme/Notes/Stories",
"dirEnv": "NOTES_VAULT_DIR",
"dirEnvSuffix": "Stories",
"idPattern": "P\\d+[a-z]?-\\d+",
"groupLabel": "Phase",
"groupSummary": "Phase {group} summary.md",
"newId": "P{group}-F{n}",
"ownerTask": { "title": "\\(owner task\\)|\\[owner\\]" }
}
foreman config check reports a bad value with the key that holds it, and
foreman.config.json lists the block beside the
Jira and Salesforce ones. Owner tasks have their own page: Owner tasks.
Was this page useful? Something missing or wrong?Email us and say which section.