Skip to content

Extraction versions

Every time you extract a story, the run becomes a version of that story. Versions are numbered per story, and every task belongs to the run that produced it: tasks cannot be created or deleted by hand, and a task is only removed together with its story.

The number is allocated when the run starts, not when it finishes: the server takes the highest number the story already has, adds one, and writes the pending run under it. That is why the version selector can list a run that is still in flight or that failed — its number is already spent. Two runs started at the same instant can collide on the number; the server retries the allocation and, if every attempt loses the race, refuses the new run with HTTP 409 and VERSION_ALLOCATION_CONFLICT. Nothing was created in that case, so retrying is safe.

The story detail shows a version selector that lists every run, newest first. Each entry names the version number, the model used, and the date; failed runs are listed too. The most recent completed run is the current version. Selecting a version shows that version’s own tasks, not the current version’s.

GET /api/v1/stories/{story_id}/versions returns that list, ordered by version number descending, with two derived booleans per entry: is_current marks the first completed entry — a story with no completed run has no current entry at all — and has_output says whether the run produced tasks.

When a newer run completes, the old version’s tasks stay viewable through the selector, but the version becomes frozen. A story with no completed run has no current version either, so its tasks are frozen too. On a frozen version you cannot:

  • edit a task’s dependencies,
  • mark a task as invalid,
  • revoke an invalidation mark.

Any of these is refused with HTTP 409 and the error code TASK_VERSION_FROZEN; the response names the story’s current version number, or null when it has none. A task’s state and labels stay editable in every version.

Marking a task invalid requires a reason and records which user marked it and when. Only the workspace owner or an admin can mark a task invalid or revoke a mark. The reason must not be blank and is at most 500 characters; a blank or over-long one is refused at body validation with HTTP 422.

  • A task carries at most one active mark; marking it again is refused with HTTP 409 and the error code TASK_ALREADY_MARKED, and the response carries the live mark’s reason.
  • Revoking a mark does not delete anything: the mark’s history — its reason, who marked it, who revoked it, and when — is preserved.
  • Revoking a task that has no active mark is refused with HTTP 404 and ENTITY_NOT_FOUND.
  • Both operations update the story’s stored vectors, so if Qdrant is configured but unreachable they are refused with HTTP 503 and VECTOR_STORE_UNAVAILABLE.

You can read a task’s full mark history (GET /api/v1/tasks/{task_id}/invalidations, revoked marks included), and the story page shows the active mark of each of its tasks at a glance (GET /api/v1/stories/{story_id}/invalidations).

Asking whether a mark repeats across versions

Section titled “Asking whether a mark repeats across versions”

Because re-extracting a story produces a fresh set of tasks, the same problem can be marked invalid, fixed, and then marked invalid again in the next version without anyone noticing. GET /api/v1/tasks/{task_id}/invalidations/repetition answers exactly that: it looks for active marks on the story’s other versions whose task title matches this task’s title after normalization, and returns their version number, reason and date.

CodeHTTPMeaning
TASK_VERSION_FROZEN409The task belongs to a superseded version
TASK_ALREADY_MARKED409The task already carries an active mark
VERSION_ALLOCATION_CONFLICT409The version number could not be minted; no run was created
ENTITY_NOT_FOUND404No such task, story or active mark
VECTOR_STORE_UNAVAILABLE503The vector store is configured but unreachable
REQUEST_VALIDATION_FAILED422The reason is blank, or longer than 500 characters