Change stacks and decisions

A change stack is a run's diff arranged for review. A decision is a choice an agent made during a run, drawn from what it said. A station turns each on in its station fields:

Station fieldEffect
diffs: trueAfter each run, a change stack is drafted for each checkout that changed.
checkEach change stack drafted at the station is checked with this command, so it has effect only with diffs: true. With both set, every repository of the line must have setup.
decisions: trueAfter each completed run, decisions are drafted from it.

Both are drafted by Spotlight after the run, not by the agent. Both need the deployment to have drafting set up; where it does not, a change stack is refused with Change stacks are not set up on this deployment., and no decisions are drafted. Reviewing an agent's changes gives the reasons.

Change stacks

States

StateMeaning
draftingThe diff is received and the stack is being drafted.
readyEvery change in the diff is placed in exactly one layer.
failedDrafting did not finish. The stack carries the reason.
MessageCause
Spotlight couldn't draft the change stack.Every drafting attempt failed.
Drafting was interrupted before it finished.Drafting stopped without a reason.

Drafting is attempted at most 4 times. Each attempt may take 30 minutes, and the wait before the next starts at 30 seconds and doubles. A diff is at most 6 MiB and 2,000 changes.

Structure

A stack is an ordered list of cohorts. A cohort is an ordered list of layers.

PartHolds
CohortA kind, a title and an intent.
LayerA title, a summary, the changes it places, a note on each file it changes, and the earlier layers it depends on with the reason for each.
Cohort kindHolds
changeThe changes a reviewer reads, in layers that build on one another.
incidentalLockfiles, generated files, test snapshots, pure renames and formatting-only changes. Spotlight places these itself.

A diff whose changes are all incidental gives a ready stack at once.

Languages read across files

Before drafting, Spotlight reads the changed files in these languages, before and after the change. It reads only the lines the diff shows, the changed lines and their context, and leaves the other lines blank. Binary files are not read. Each side of a file is read only when that text is at most 524,288 characters.

Spotlight links each change, a hunk of the diff, to the hunks that define a name it uses or imports, or a route it names. The hunks may be in the same file. A hunk is not linked to itself. Route links come only from the after side.

LanguageFiles
TypeScript.ts, .mts, .cts, .tsx
JavaScript.js, .mjs, .cjs, .jsx
Vue.vue: the script blocks, and the component tags of the template
Svelte.svelte: the script blocks, and the component tags of the markup
Python.py
Go.go
Java.java
Ruby.rb, .rake
Rust.rs
PHP.php
C#.cs
Terraform.tf

Routes are read in every language above but Terraform:

  • from a call whose first argument starts with /, where the call is named for an HTTP method (get, post, put, patch, delete, head, options) or is all, any, route, handle, handlefunc or match, in TypeScript, JavaScript, Vue, Svelte, Python, Ruby, Rust and Go;
  • from Nest decorators, with the controller's prefix, in TypeScript, JavaScript, Vue and Svelte;
  • from route annotations in Java, attributes in PHP and C#, Route:: and other route calls in PHP, and Map calls such as MapGet in C#;
  • in Go, also from a pattern that starts with its method, as in "GET /tasks/{id}", given to Handle, HandleFunc, route, match, any or all, with or without a host before the path.

A route links only when its path has at least 2 segments that are not wildcards.

Checks

A station's check command is run against the stack in a sandbox of its own, from the line's runner image, on a runner that meets the run's placement.

StepChecked tree
The whole changeThe run's diff applied in full. Always first.
Each change layer, in orderThe incidental changes and every layer up to and including this one.
  • Each checked tree borrows git objects from the host's mirror of the repository, when the host has one.
  • Each repository's setup runs before the first step, and again before any step whose lockfile changes differ from the step before.
  • When the whole change fails, the layers are not checked.
  • When the whole change passes and a layer fails, the stack is redrafted once and checked again.
  • A step may take 5 minutes, and a check 60 minutes in all. The last 16,000 characters of each step's output are kept.
  • A runner holds a check under a 10-minute lease, which it renews while the check runs, with POST /api/runs/{runId}/change-stack-checks/{stackId}/lease. A renewal is refused 65 minutes after the claim. The hold ends when the lease lapses, or 65 minutes after the claim.
  • A check is claimed at most 3 times.
  • A check that has waited more than 60 minutes is skipped with nobody-checked while no runner holds it.
Check stateMeaning
waitingWaiting for a runner to claim and run it.
checkedEvery step ran. Each step passed or failed.
skippedThe check did not run. It carries a skip code.
CodeCause
no-baseSpotlight doesn't know the commit where the run began. The layers can't be checked without it.
no-checkoutSpotlight does not know which checkout the changes are from.
checkout-failedSpotlight could not lay out a copy of the checkout to check.
setup-failedThe setup of the checkout failed before the check ran.
no-roomThe runner had no room to check the layers.
out-of-timeThere was no time left to check the layers.
no-imageThe host couldn't pull the runner image that the line names.
nobody-checkedNo runner checked the layers in time. The check had waited more than 60 minutes, and no runner held it.
unprovisionedSpotlight couldn't provision a runner to check the layers.

A push pool receives a check as provision work of kind check. See Provision call.

Where a change stack is shown

  • The run's change stack page in the web app, reached from the task's "Changes" property. See Web app.
  • A comment on the task's pull request, kept up to date with the latest ready stack, when the workspace is connected to GitHub.

Decisions

Kinds

KindLabel in the pull request comment
trade-offTrade-off
assumptionAssumption
limitationLimitation
rejected-approachRejected approach
deviationDeviation

Fields

FieldTypeDescription
kindstringOne of the kinds above.
titlestringAt most 120 characters.
whystringThe reasons, at most 2,000 characters.
alternativesarray of stringsWhat was weighed and set aside. At most 5, each at most 300 characters.
pathsarray of stringsThe files the decision bears on. 1 to 20.
stepsarray of integersThe steps of the run the decision is drawn from. 1 to 20.
disputesarray of objectsEach has the file, the person and the reason.

A drafted decision that breaks these limits is dropped.

Drafting

  • Decisions are drafted after a run completes at a station with decisions: true, unless the task reviews a pull request.
  • A task has one set of decisions. Each later drafting replaces it.
  • Drafting is attempted as for a change stack. When every attempt fails, the task keeps the decisions it had.

Standing

StandingMeaning
proposedDrafted, and open to disputes. Later runs may replace it.
standsThe task's pull request merged. Nothing is drafted for the task again.

When the pull request is closed without merging, the decisions are discarded. Decisions that stand are listed by GET /api/workspaces/{slug}/decisions, filtered by repository, path, since and until (merge times).

Disputes

A member of the task's team disputes a decision by replying to it on one of its files in the change stack page. A reason is 1 to 1,000 characters.

Where decisions are shown

  • The change stack page, beside the files each decision bears on.
  • A comment on the task's pull request, when the workspace is connected to GitHub. It is withdrawn when the task has no decisions.
  • The briefing of each later run on the task, under ## Decisions on record, with each dispute. A run at a station with decisions: true is also briefed under ## Say what you decide. See Briefing.

On this page