slurmpast: Post-Mortem Analysis for Finished HPC Jobs

Sat, Aug 15, 2026 23-minute read

slurmpast is an open-source command-line tool that reads what a set of finished jobs actually did, and states what the next submission should ask for. It is the counterpart to a tool introduced here earlier: slurmwatch reads a job while it is running; slurmpast reads the accounting record after it has stopped.

pip install slurmpast
slurmpast              # dashboard, last 7 days
slurmpast --sizing     # what to request next time, per workload
slurmpast 51170455     # one job, every field the scheduler recorded
slurmpast --demo       # no Slurm to hand? a synthetic history
sp                     # short alias

The slurmpast dashboard driven end to end: finished jobs rolled into workloads ranked by resource use, filtered to those that went wrong, into a workload that timed out fourteen times at an unchanged limit, into one run's post-mortem showing the full wall clock against no processor activity and the finding that says it hung rather than ran out of time, then the cross-run patterns and the machine reliability table.

It requires Python 3.10 or later and depends on textual and rich for the interface; every module that performs analysis imports nothing outside the standard library. The licence is MIT.

This article describes the package, and then uses it on a real accounting history to extend an argument made in an earlier article, Correcting Claude Code’s Cluster Resource Requests with slurmwatch. That article established that Claude Code requests far more memory and processor time than its jobs consume, and closed with a remedy: take a live reading and size the request against it. The remedy is correct and it does not scale, for a reason that can now be measured. A live reading requires a person to be present while the job runs. The accounting record requires nobody, and it has already been written.

Every number below comes either from the package’s own source and tests, or from one Slurm accounting history on a university cluster.

Three instruments, three moments

The three questions that arise around a submission need three different instruments, because the evidence for each exists at a different time.

Before the run. What should be requested for work that has never been run? slurmate constructs the request.

During the run. Is the job using what it was given? slurmwatch reads the compute node live. It is the only one of the three that can measure a job’s genuine memory requirement, because that quantity exists only while the job is running.

After the run. What happened, and what should the next submission ask for? That is slurmpast. It is the only one of the three that reads every run of a piece of work at once, and several of the most expensive mistakes in this record are invisible within any single run.

The instrument

The workload rollup

The accounting history used below holds 13,954 job records. A list of those is not an answer to any question, so the opening screen groups runs into workloads and ranks them by the compute they consumed, which places a five-run group costing four hundred GPU-hours above four hundred two-second probes.

Grouping is not by job name. Names encode parameters, so raw names barely group at all; collapsing runs of digits folds s1e20 and s2e47 into one pattern, s#e#, and a column records how many real names each pattern covers. On the portion of this history attributable to Claude Code, 523 distinct names fold to 470 workloads, the twenty most expensive of which carry 62.8 per cent of the compute.

  58 jobs in 8 workloads · 56.9% completed · 184 GPU-hours total, 91 of them never used
  ordered by compute used (1 GPU-hour = 16 CPU-hours) · "#" stands for a name's digits

  #    JOB NAME               PARTITION  RUNS  FLAGGED CPU / GPU-HOURS LAST RUN
  -------------------------------------------------------------------------------
  1    node-evaluation        test          1        1     336 / 84    2026-07-26
  2    midtrain               test         14        -     105 / 79    2026-07-18
  3    cot-exp                test         20       14      61 / 10    2026-07-20
  4    ddp-pretrain           test          1        1      48 / 6     2026-07-22
  5    att-speed-#            test          7        -      56 / 5     2026-07-12

That listing, and every other terminal output shown in this article, is slurmpast --demo, the synthetic history the package ships so that the tool can be evaluated without a scheduler. Real job names, script names and project names are omitted throughout; real workloads are described by the work they performed.

One job against its own limits

A job identifier produces the post-mortem for that job: the three quantities that have a ceiling drawn against that ceiling, then every field the scheduler recorded, the log excerpts if the files remain on disk, and the findings.

job 5100044  COMPLETED

  ● TIME   █░░░░░░░░░░░░░░░░░         4.2%   · 00:20:00 of the 08:00:00 limit
  ● CPU    █░░░░░░░░░░░░░░░░░         5.9%   · 0.9 of 16 cores busy
  ● MEM      no percentage        32.5 GiB   · an upper bound, over the 32.0 GiB limit

  findings

  [WARN] Peak memory reads above the limit, yet nothing was OOM-killed
        32.5 GiB against a 32.0 GiB per-node limit, so it is not this job's footprint:
        MaxRSS sums RSS across the process tree under jobacct_gather/linux,
        double-counting shared pages, so treat it as an upper bound.
        -> Measure the cgroup working set live (slurmwatch) before sizing --mem.

  [INFO] Walltime request much larger than needed
        Used 00:20:00 of the 08:00:00 limit (4.2%).
        -> A tighter --time reaches backfill windows a long request cannot enter.

Two properties of that excerpt are deliberate. The memory row declines to print a percentage, because the figure above it exceeds the limit and a bar overflowing its own track asserts something about a measurement that is not true of it. And the finding raised in its place refers the question to the other instrument: the record cannot settle this one, and says so rather than guessing.

The reason it cannot is worth stating once, because it constrains everything that follows. MaxRSS is the only memory figure the scheduler retains for every job, and under the process-tree accounting used on many systems it sums memory across processes, counting shared pages once per process. It therefore reads high, sometimes by a large factor. Any advice derived from it inherits that bias.

The next request, derived from the previous ones

--sizing converts the history into a request. It reports on the three flags that are actually written by hand, and where fewer than three usable runs support a verdict it states not enough evidence rather than producing one.

what to request next time
  From how each workload actually ran. Over-requesting narrows which nodes can host it;
  under-requesting kills the run.

  midtrain  test · 14 runs
    --time            raise to 02:30:00   (from 02:00:00)
        longest of 14 completed runs took 01:52:49.
    --mem             already about right
    --cpus-per-task   already about right
    #SBATCH --time=02:30:00

  cot-exp  test · 20 runs
    --time            no advice
        14 of 14 timed-out runs consumed almost no CPU before hitting the wall: blocked,
        not slow -- a longer limit buys a longer hang.
    --mem             lower to 3G   (from 80.0 GiB)
        the most any run used was 1.9 GiB.
        ! MaxRSS sums RSS across the process tree under jobacct_gather/linux,
          double-counting shared pages, so treat it as an upper bound.

Each verdict is measured against the limit the most recent run requested, not the largest in the window. Those two differ from the moment a request is being tuned, and the window is deliberately constructed to span that tuning: grouping ignores resource magnitudes, so raising --mem does not fork the history one is attempting to learn from.

Patterns that exist only across runs

Three checks examine a workload rather than a job, and none is answerable from a single run.

The first is repeated failure. seff describes one job and slurmwatch describes one live run, so neither can report that a piece of work was submitted 115 times and died 99 times. Where every failing run used an identical request, the tool says so, which distinguishes a limit that is too small from a program that is broken.

The second is a memory request being searched by hand: a sequence of out-of-memory kills with the request stepping up and down. Where the same value both failed and later succeeded, the tool draws the conclusion that follows and declines to recommend a further increment.

The third is allocations that computed nothing: runs that held their resources for hours while consuming almost no processor time.

cross-run patterns

  [FAIL] Memory request is being hand-searched
        8 OOM kills for rc-tok-github_code with --mem walking 32.0 GiB -> 32.0 GiB ->
        17.0 GiB -> 12.0 GiB -> 12.0 GiB -> 14.0 GiB -> 16.0 GiB -> 18.0 GiB. Job
        5100044 then COMPLETED at 32.0 GiB -- a value that had already OOM'd.
        -> --mem is not the deciding variable: the same request both failed and
           succeeded. Something else changed (worker count, batch size, input shard).
           Find that before tuning memory again.

  [WARN] This work has failed repeatedly in the same way
        14 of 20 runs of cot-exp in test failed; 14 were TIMEOUT. Every one used the
        same --time=00:30:00. 7 GPU-hours consumed by the failures.
        -> 14 of these consumed under 10 CPU-seconds -- they hung rather than ran out of
           time. Raising the limit will not help; fix the blocking call.

Machine reliability, controlled for workload

A job may also fail for reasons unconnected to the person who wrote it. --nodes reports each machine’s failure or hang rate with a confidence interval and, where the evidence supports one, a ready-to-paste --exclude.

Two corrections stand behind that screen, and both were introduced because their absence produced a wrong answer. Placement is not random, so the comparison is made within a single workload; without that control one machine appeared twenty-five per cent worse than the fleet almost entirely because a defective campaign had happened to land there. And every row of the table constitutes a hypothesis test, so testing each interval in isolation manufactures a culprit once the table is large enough: given twenty machines sharing one identical true failure rate, that procedure offered an innocent machine for exclusion in 54.8 per cent of tables, and in 77.8 per cent of forty-machine tables. A Benjamini-Hochberg correction across the rows holds the error near 2.7 per cent and, more importantly, holds it flat as the table grows.

The accounting traps underneath

The sacct record is not a clean instrument, and its failure modes are easy to overlook. Each entry below was found on a real record, and each corresponds to a regression test naming the job identifier that produced it.

Trap Reality
ReqMem reads 0n on 2,130 of 6,574 jobs the real ceiling is in AllocTRES
AllocTRES mem= is the allocation total --mem and the cgroup are per node, so a two-node --mem=8G job records mem=16G
MaxRSS reports 51.25 GiB against a 40 GiB limit that killed the job it sums memory across the process tree, counting shared pages once per process
MaxRSS differs by a factor of 4,000 between steps of one job take the maximum, not a step
TotalCPU exists only on steps read it from .batch
State=RUNNING, End=Unknown months after death elapsed time becomes now minus start; one such record was 65 per cent of a GPU-hour total
sacct --state=X returns zero rows without -E always pass an end time

Any quantity that cannot be read prints n/a and never 0. Field names, timestamp formats, host-list syntax and the spelling of GPU resources all vary between scheduler versions and sites; each is negotiated at run time rather than assumed, and each is covered by a portability test.

The record these measurements come from

The accounting history holds 13,954 usable records for one account, after excluding 357 synthetic sleeper jobs written to exercise slurmwatch, which would otherwise distort every distribution here.

Attribution to Claude Code rests on the job identifier that sbatch prints back into a session transcript. That accounts for 991 submissions and, counting array tasks individually, 6,744 allocations, and those are the jobs used wherever this article discusses requests. The remainder cannot be attributed, for two structural reasons: 5,614 records predate the oldest session transcript still on disk, and a further 1,596 inside the transcript window were launched by a driver script or by hand, so no identifier was ever printed into a transcript. The agent’s own record of its work is partial and expires; the scheduler’s does not.

The 6,744 allocations attributable to Claude Code, by how long each one ran. A live reading has to be taken while the job occupies the node, so the two red bars are out of reach in practice and much of the third is as well. The accounting record covers all five bars equally, and is written whether or not anyone intends to read it.

Figure 1: The 6,744 allocations attributable to Claude Code, by how long each one ran. A live reading has to be taken while the job occupies the node, so the two red bars are out of reach in practice and much of the third is as well. The accounting record covers all five bars equally, and is written whether or not anyone intends to read it.

This is the practical ceiling on the earlier article’s remedy. That article reported live readings for 21 jobs, and 21 was not a failure of diligence: it is approximately the number of runs a person can stand over. The record covers three hundred times as many, and consulting it costs nothing, because the writing was done by the scheduler.

Three failures no single reading can reach

A memory request settled by trial and error

The clearest single case in the record is a tokenizer run over a corpus of source code. It was submitted ten times in one evening, and the memory request changed on almost every attempt.

Ten submissions of one tokenizer, in the order they were made. The grey point is the memory requested; the coloured point is the highest figure the scheduler recorded for that run. Submissions two and three died before any figure was sampled. Where the coloured point lies to the right of the grey one, the run was killed while exceeding its limit.

Figure 2: Ten submissions of one tokenizer, in the order they were made. The grey point is the memory requested; the coloured point is the highest figure the scheduler recorded for that run. Submissions two and three died before any figure was sampled. Where the coloured point lies to the right of the grey one, the run was killed while exceeding its limit.

Eight of the ten runs were killed for memory. The request descended from 48 GiB to 12 across the first six submissions while those kills were occurring, which is the opposite of the direction the evidence supported, then climbed back. The run that finally succeeded held 32 GiB, a value already killed twice at the start of the evening, and reached 24. Something other than the memory request was the deciding variable throughout, which is precisely the conclusion the cross-run check draws and the reason it refuses to recommend a further increment.

The sequence occupied a sixteen-core allocation for one hour of compute and nine hours of wall clock. Every fact required to stop after the third attempt was in the accounting database by then.

An array sized from two unrepresentative tasks

The second case is the more interesting of the two, because it is the failure mode of following the earlier article’s advice correctly.

A campaign scanned a text corpus in 142 shards, one array task per shard. Two single-task probes were run first; they reached 1.00 and 0.72 GiB against an 8 GiB request. Sized against those readings, the full array was submitted at --mem=6G, and 71 of the 142 tasks were killed for memory.

Per-task peak memory for all 142 shards, sorted from smallest to largest, taken from the corrected rerun so that no measurement is truncated by a kill. The two probes the request had been sized from are marked at their position in that order: both fall inside the smallest fifth of the corpus. Fifty-nine shards exceed 6 GiB here, while 71 were actually killed at that request, because a peak sampled every few seconds understates a brief spike.

Figure 3: Per-task peak memory for all 142 shards, sorted from smallest to largest, taken from the corrected rerun so that no measurement is truncated by a kill. The two probes the request had been sized from are marked at their position in that order: both fall inside the smallest fifth of the corpus. Fifty-nine shards exceed 6 GiB here, while 71 were actually killed at that request, because a peak sampled every few seconds understates a brief spike.

Thirty-seven hours of task time were destroyed and recovered within forty-one minutes, which is an inexpensive version of this mistake. The general point is that a live reading measures the run that was watched. Where the work is one long job, that is the same thing as measuring the workload, and it is why the earlier article’s remedy succeeded on training runs. Where the work is an array over heterogeneous input, or a pipeline whose phases differ in footprint, one reading is a sample of size one, while the quantity that decides survival is the maximum over the whole population.

Repetition under an unchanged request

Across the whole record, 71 workloads ran at least five times and failed at least three. In 40 of them every failing run used an identical memory request and an identical time limit.

The twelve workloads with the most failures. Each is named by the name its own submission carried, since that is what the record supports: five of the twelve are exploratory runs called some variant of 'test', which fail often and are expected to. The faint bar is every run; the solid bar is the runs that failed. Red marks a workload in which every failing run carried an identical memory request and an identical time limit, so the request was never varied in response to the failures.

Figure 4: The twelve workloads with the most failures. Each is named by the name its own submission carried, since that is what the record supports: five of the twelve are exploratory runs called some variant of ‘test’, which fail often and are expected to. The faint bar is every run; the solid bar is the runs that failed. Red marks a workload in which every failing run carried an identical memory request and an identical time limit, so the request was never varied in response to the failures.

The reasoning experiment in the third row is the case the check was written for. Its 99 timeouts consumed 300 core-hours and 50 GPU-hours, every one of them at the same thirty-minute limit, and the verdict the tool returns is not “raise the limit”. The timed-out runs consumed almost no processor time before reaching the wall, which means they were blocked rather than slow, and a longer limit purchases a longer hang. That distinction requires both numbers together, and it is the difference between diagnosing the failure and paying for it a hundredth time.

Taken across all 71 workloads, the failures under an unchanged request account for 458 of the 1,206 failed runs.

The verdicts the record produces

Applying the sizing engine to every workload attributable to Claude Code produces a verdict per flag, or an explicit refusal where fewer than three runs support one.

Verdicts over the 470 workloads. The refusals are excluded from the bars and counted in the axis labels instead: most workloads in any real history are run once or twice, and three usable runs are required before a verdict is offered.

Figure 5: Verdicts over the 470 workloads. The refusals are excluded from the bars and counted in the axis labels instead: most workloads in any real history are run once or twice, and three usable runs are required before a verdict is offered.

The processor row does not follow the pattern: it recommends an increase as often as a reduction. Most of those increases are single-core tasks that kept their one core saturated, and a saturated resource is evidence that more of it might help. On this history the recommendation is nonetheless frequently wrong, for a reason taken up under limitations.

The individual memory verdicts, for the workloads that consumed the most compute, are below.

The twelve most expensive workloads for which the record produces a memory verdict. The grey point is what the most recent run requested; the coloured point is what the record recommends. The vertical tick, and the middle number column, are the largest figure ever recorded for that workload. That is the quantity the recommendation is derived from, with a thirty per cent margin added.

Figure 6: The twelve most expensive workloads for which the record produces a memory verdict. The grey point is what the most recent run requested; the coloured point is what the record recommends. The vertical tick, and the middle number column, are the largest figure ever recorded for that workload. That is the quantity the recommendation is derived from, with a thirty per cent margin added.

The four increases are the valuable part of that figure and the part no cost-minimising rule would produce. In the gating experiment, the most recent run requested 24 GiB for work that had already reached 34.56 in an earlier run. The time limit produces two more of the same kind, not shown above: a family of experiments whose recent runs requested thirty-five minutes, where the longest completed run of that same work took four hours and ten minutes; and a pretraining slice whose longest completed run finished one minute and sixteen seconds inside an eight-hour limit. Neither had failed at the point the record was read.

The value of acting on them

Every allocation attributable to Claude Code, re-costed under the request its own history recommends. 'Used' is what the runs consumed: recorded peak memory held for the duration, processor time actually burned, and wall clock actually elapsed. Memory usage here rests on MaxRSS and is therefore an upper bound, so the true gap in the first panel is wider than shown.

Figure 7: Every allocation attributable to Claude Code, re-costed under the request its own history recommends. ‘Used’ is what the runs consumed: recorded peak memory held for the duration, processor time actually burned, and wall clock actually elapsed. Memory usage here rests on MaxRSS and is therefore an upper bound, so the true gap in the first panel is wider than shown.

Three readings, and the second is the honest limit of this whole approach.

The time limit collapses, because a completed run’s duration is an exact measurement and the recommendation is that duration plus a quarter. An over-long limit costs queue position rather than capacity, so it is the cheapest of the three to get wrong; it is also the one the record settles outright.

Memory closes about a fifth of its gap. The recommendation is derived from MaxRSS, which reads high, and adds a thirty per cent margin to a figure that is already an over-estimate. It is therefore systematically generous, and deliberately so, because a memory recommendation that is too small destroys a run. The remaining four fifths is exactly the territory a live reading of the working set can reach and the record cannot. The two instruments are not substitutes for one another.

Processor time moves in the wrong direction, by a fifth, because of the single-core increases. Accepting only the recommendations that reduce a request converts that into a saving of 4.9 per cent.

Allocations that held a card and computed nothing

An allocation counts as having computed nothing when it held its resources for the whole of its life while consuming essentially no processor time. A cancelled run that had already performed useful work does not qualify. The two rows are drawn as shares because the record as a whole holds eighteen times the graphics-card hours of the attributable portion; the hours themselves are printed inside the bars.

Figure 8: An allocation counts as having computed nothing when it held its resources for the whole of its life while consuming essentially no processor time. A cancelled run that had already performed useful work does not qualify. The two rows are drawn as shares because the record as a whole holds eighteen times the graphics-card hours of the attributable portion; the hours themselves are printed inside the bars.

This is a post-mortem finding by construction. Nothing distinguishes a hung allocation from a working one at the time unless somebody looks; afterwards it is unmistakable, because the record shows hours of wall clock beside a processor total of a few seconds. The attributable portion performs considerably better than the record as a whole, which is the one comparison here that favours the agent: it launches work and moves on, so it rarely leaves an interactive allocation running overnight.

Failures that belong to the machine rather than the job

Hang rate by machine within a single workload of 1,098 placements, so that differences in the work itself cannot explain the differences between machines. Bars are 95 per cent Wilson intervals. Each machine is compared against that same workload on every other machine, computed with the machine under test left out. Eight machines had too few placements to test and are omitted, and one further machine's interval clears the baseline without surviving the correction for testing eleven rows at once.

Figure 9: Hang rate by machine within a single workload of 1,098 placements, so that differences in the work itself cannot explain the differences between machines. Bars are 95 per cent Wilson intervals. Each machine is compared against that same workload on every other machine, computed with the machine under test left out. Eight machines had too few placements to test and are omitted, and one further machine’s interval clears the baseline without surviving the correction for testing eleven rows at once.

Node I received 403 of the 1,098 placements, more than any other machine, and is significantly better than the fleet. That is the confound the workload control exists to defeat: a table pooling the baseline across all placements would have compared Node I against a rate that was itself 37 per cent composed of Node I, dragging the comparison toward whatever that machine happened to do and concealing precisely the machines that ran the most work.

For an automated agent the practical value is separate from the statistics. Forty-five runs of this workload hung on the three machines the table names, holding 689 graphics-card hours between them. An agent reading only its own exit codes cannot reach that conclusion; it will reread its own code instead, which is what makes this the most expensive category of failure to diagnose.

The coverage of each instrument

Collecting the failures documented above, the matrix below records what each available signal reveals about each of them. It is deliberately unflattering to the tool this article introduces.

What each signal reveals about each failure documented in this article. 'A live reading' is slurmwatch on the run being watched; 'the accounting record' is slurmpast over every run of the work. Two cells deserve comment. A live reading is marked misleading on the array because the two tasks that were read peaked at about a seventh of what the corpus required. The accounting record is marked misleading on an over-large memory request because MaxRSS reads high, so sizing against it leaves capacity reserved that nothing will use.

Figure 10: What each signal reveals about each failure documented in this article. ‘A live reading’ is slurmwatch on the run being watched; ‘the accounting record’ is slurmpast over every run of the work. Two cells deserve comment. A live reading is marked misleading on the array because the two tasks that were read peaked at about a seventh of what the corpus required. The accounting record is marked misleading on an over-large memory request because MaxRSS reads high, so sizing against it leaves capacity reserved that nothing will use.

The log, which is the one artefact almost every workflow actually inspects, reveals none of the seven. The exit status reveals five of them one run at a time, which is precisely the mode of failure that produced ten submissions of one tokenizer in an evening: each kill was informative, and none of them carried the previous nine. The single row on which the accounting record is not the answer is the first, which concerns the level of an individual run rather than a pattern across runs. That row belongs to the live reading, and it is the reason both instruments exist.

Interfaces for automated use

Everything above is available without the dashboard. --plain prints the same content as text, and --json emits all of it, 174 values per job, from analysis modules that import no third-party package.

from slurmpast import History, Sacct
from slurmpast.sizing import recommend

history = History(Sacct().history(user="you", since="now-30days"))
for advice in recommend(history.groups[0].jobs):
    print(advice.flag, advice.verdict, advice.suggestion)

The cost is what allows this to be a habit rather than an investigation. Over the full history used here, the sacct query takes 5.1 seconds; parsing it, grouping the runs into workloads, producing a sizing verdict for every workload and running the cross-run checks together take 0.4 seconds. There is no cache and no database, because at that price neither would justify its complexity.

The corresponding instruction is correspondingly small. Before submitting work that has been run before, read what it did last time; before resubmitting work that has failed, establish whether it has already failed in this way. Both are a single command, and the second is the one that repays the effort, because the strong prior on a failed job is to change the code.

Limitations

The processor recommendation is wrong at the bottom of its range. A job that saturates its single core is advised to request two, because the rule multiplies the observed peak by a fixed margin and rounds up, and one core times 1.2 rounds to two. For a genuinely single-threaded program that is not merely useless but inverted, and on this history it is frequent enough to convert a 4.9 per cent saving into a 19.6 per cent increase. The premise, that a saturated resource may be a bottleneck, is sound; the margin does not belong at a scale where the next available value is double.

The memory recommendation inherits MaxRSS. It is the only memory figure the scheduler retains for every job, and it reads high, so advice built on it errs in the direction that reserves capacity rather than the direction that destroys runs. The tool states this at the point of advice rather than in a footnote, and refers the question to slurmwatch. This is a genuine ceiling on what a post-mortem can achieve alone.

A verdict requires three runs. Most workloads in any real history are run once, and for those the record has nothing to say. The refusal is reported rather than filled in, which is correct, and it also means the coverage in the figures above is a minority of the workloads even though it is most of the compute.

The thresholds are heuristics from one cluster. What is measured is portable; what is judged is calibrated. Each threshold is a named constant, so recalibration means editing a number rather than the logic.

Attribution to Claude Code is partial, for the two structural reasons given earlier. The figures describing requests use only the attributable portion; the figures describing the record as a whole say so.

The record the agent does not keep

The earlier article concluded that Claude Code is not poor at HPC work but blind to one specific quantity, and that the remedy is an instrument rather than an instruction. This history extends that conclusion in a direction live readings alone could not show.

The blindness is not only to a measurement. It is to a history. A submission is written, a job runs, and whatever that job revealed about itself is discarded when the session ends. The transcript expires; the next session begins with no knowledge that this work has been run before and measured every time; and the request is composed again by the same reasoning that produced it originally. The tokenizer settled by trial and error, the array sized from two unrepresentative shards, the experiment that timed out ninety-nine times at an unchanged limit: none of these is a failure of reasoning about the job in front of it. Each is a locally correct decision taken without the previous ones in view.

The scheduler retained all of it. Every request, every outcome, every recorded peak, for every job either party has ever run, in a database that costs five seconds to read. What was absent was neither the data nor the diligence, but a tool that converts the record into the one sentence the next submission requires.

Availability

slurmpast is available on PyPI and GitHub under the MIT licence. It requires Python 3.10 or later and a scheduler with accounting enabled; where accounting is disabled or sacct is absent it prints a single line of explanation rather than a traceback. Continuous integration covers Python 3.10 through 3.13, both ends of the supported Textual range, and a machine with no Slurm installed at all, over a suite of 1,195 tests.

slurmpast --demo runs the entire dashboard against a synthetic history, which is the quickest way to establish whether it answers a question worth asking.