Skip to content

Releases: daus95/tempa

v0.10.2

Choose a tag to compare

@daus95 daus95 released this 19 Aug 11:52
86e1d2a

Added

  • A Back button in the toolbar when a Clarification or Specification file is open. Opening
    a clarification file — from the Clarification Overview table, the sidebar's Clarification
    file list, or the Home page's "files needing clarification" list — left no way back to the
    Clarification Overview page short of clicking "Clarification" in the sidebar tree, which
    wasn't an obvious "go back" affordance. A Back arrow now sits at the start of the toolbar
    whenever a file is open (both Clarification and Specification), returning to that section's
    overview and still prompting to discard unsaved changes first, same as before.

Changed

  • Clarified the Home page's "close working folder" button. It previously showed only a bare
    X icon relying on a hover tooltip, easy to mistake for dismissing the panel rather than
    unlinking the workspace. It now carries a visible "Close" label alongside the icon, styled as
    a bordered pill consistent with the rest of the workspace panel.

  • Moved "Clear All" off the Home page to Settings → Maintenance → Danger Zone. It previously
    sat directly beneath the Start Implementation button, one step below buttons that get clicked
    repeatedly during a normal run — an easy target for a stray click on a destructive,
    unrecoverable action. It's now under its own red-bordered "Danger Zone" group in Maintenance,
    alongside Restart Server. Behavior (including the confirmation dialog) is unchanged.

Fixed

  • Fixed six self-contradictions in the POS example PRD.
    examples/02-web-app-with-db/PRD.md ships as a reference PRD that is meant to keep the first
    clarification round short, but it disagreed with itself: the discount could be a percentage or
    a fixed amount while Sale had a single discount column to hold it; "total due" drove both
    the change calculation and the paid-amount validation without ever being defined; the Cashier
    had "POS/checkout access only" in the business process yet a Dashboard in the UI section, and
    was promised a per-shift history with no Shift entity anywhere in the data model; the void
    step was "same-day" in its heading only; Stock In recorded a supplier and a receipt price that
    StockMovement had no column for; and the receipt "prints" despite a non-goal ruling printers
    out of scope. All six are now resolved in the document — totals arithmetic spelled out, the
    discount modelled as type/value/amount, a per-role Dashboard, the same-day void rule stated in
    the step itself, and supplier + unit cost added to StockMovement — with the ERD, the
    validation list and the acceptance criteria updated to match. The tech stack, which hedged
    every choice with "or an equivalent", is now pinned: React + Vite, Node.js/Express,
    PostgreSQL + Prisma, and JWT rather than "session or JWT".

Full changelog: v0.10.1...v0.10.2

v0.10.1

Choose a tag to compare

@daus95 daus95 released this 19 Aug 07:28
c63eb20

Changed

  • Clarified the "critical findings" readiness check on the Clarification page. The checklist
    item and hint above Continue/Finalize Clarification previously read "Most recent evaluation
    still shows N critical finding(s)" even after every finding had been answered and the
    Unanswered table was empty — confusing, since that count is carried over from the last
    evaluation run and isn't updated by answering findings. Both now say the findings "haven't
    been re-checked yet" and that answering them doesn't update the count, so it's clear a fresh
    Continue Clarification run is what re-evaluates them.

Full changelog: v0.10.0...v0.10.1

v0.10.0

Choose a tag to compare

@daus95 daus95 released this 19 Aug 06:26
ce4318c

Added

  • A clarification finding's spec references are now links, and open the spec beside it.
    Deciding a finding means reading what it cites, and a **Where:** line like
    M07-FR-03 Group A aggregation rule; BR-07.2 was inert text — the ids are real, but nothing
    recorded where they live, so answering meant leaving the finding, hunting through the
    Specification tree by eye, and navigating back having lost your place in a card you were
    part-way through answering. Requirement/rule ids, §-section references and file paths
    inside a finding now resolve to a specification file and line, and clicking one slides in a
    drawer showing that file scrolled to — and briefly highlighting — the exact table row,
    bullet or heading that defines it. The drawer is modal: the page behind it dims and stops
    responding while it is open, and closing it (✕, Escape, or a click on the backdrop) returns
    the Clarification page exactly as it was. It is read-only, with an "Open in Specification"
    button for editing, and its width is draggable.

    References are resolved against the PRD at render time, not when the finding was
    written, which is what makes this work on clarification rounds already on disk and keeps the
    line numbers from ever going stale. Only ids the PRD actually defines — in a heading, a
    requirement table's id column, or a bold lead-in — are linked, which is what keeps a
    finding's references to earlier findings (R8 M2) as plain text without hard-coding any
    project's id vocabulary; an id defined in two different files is left unlinked rather than
    guessing. clarification.md now also asks new findings to name the spec file in their Where
    line, so a finding that cites no id at all still gets a link.

  • The dashboard's left navigation can be collapsed to an icon rail (the button beside
    Refresh, or Ctrl/Cmd+B), giving the findings and the spec drawer the width they need. The
    rail keeps every top-level section one click away and still shows the unanswered-findings
    count. The collapsed state and both panel widths are remembered for the session.

  • Dashboard Home page now shows recent working folders and can create a new one. The empty
    state (no working folder selected yet) previously offered only "Select Working Folder",
    forcing a re-navigation through the native picker even for a project you had already opened
    before. It now also lists up to the 10 most recently opened working folders (newest first) —
    click one to reopen it, or the ✕ to drop it from the list — and a "Create New Working Folder"
    button that picks a parent location, prompts for a name, and runs init on the new path.
    Recent folders are tracked in a new install-root .workspace-history.json, updated on every
    init and close-folder.

Full changelog: v0.9.0...v0.10.0

v0.9.0

Choose a tag to compare

@daus95 daus95 released this 18 Aug 11:29
5bea225

Fixed

  • An epic is no longer failed over work Tempa itself terminated. A session did 182 turns of QA
    fixes, live-verified three features, started a twelve-minute test suite in the background and
    closed its turn saying it would report back once it finished. Under a headless one-shot CLI run,
    writing that sentence is how a session ends: the CLI exited 0, Tempa's own container teardown
    reclaimed 38 processes including the suite, and the result the round was waiting for never
    existed. The next round started over with no memory of the verification, did the same thing, and
    reclaimed 35 more — and two exit-0 rounds with no feature completed is exactly what
    implement_no_progress_rounds reads as an epic blocked on something outside itself. The epic was
    marked failed and the whole runner stopped, over an epic that was blocked on nothing.
    Tempa already had a guard for this shape, but it could not fire: background_tasks_terminated_hit
    is set only by recognising the backend CLI's own "Background tasks still running after Ns;
    terminating." message, which covers background work that CLI's harness tracks — not a shell the
    session started with its Bash tool's run_in_background, for which the CLI prints nothing and
    exits 0. Tempa's own kill path set nothing at all. The teardown is now recorded on _state, and a
    round that both promised to come back with a result AND had processes reclaimed earns the epic one
    extra round before the count is allowed to matter.
    Deliberately an allowance, not an exemption. Both signals are required because either alone is
    wrong in a direction that matters: leftover processes were reclaimed on 82% of exit-0 sessions
    in the workspace this came from (62 of 76), so a grace keyed on reclaim alone would just be
    implement_no_progress_rounds: 4 wearing a mechanism costume, and would make a genuinely blocked
    epic that happens to leave a vite server running effectively unfailable. The allowance is one
    round, is dropped the moment a feature completes, is cleared by --reset-failed, and is spent
    only in the failure arm — so the two fully automatic repairs beside it (the dependency reorder and
    the QA-state desync repair) still fire on exactly the round they always have.
  • A halt no longer claims a blocker Tempa has no evidence for. The failure message asserted the
    epic was "very likely blocked on something outside this epic" and then conceded, one line later
    and verbatim, that "the session didn't name a specific epic it's blocked on". The claim is the
    part a human reads first, and it sent them hunting a dependency that did not exist. When no
    blocked_by_epic was named, the halt now states only what is known; when the round was one Tempa
    cut short, it says so in Tempa's own voice and quotes the session's words underneath rather than
    instead of them. That quoting is load-bearing: the incident round's closing message was the only
    record anywhere that three features had been finished, since the round ended before writing them
    into config.json.
  • The next round is no longer told to redo the thing that killed the last one. last_round_note
    is handed to the following session as a "CLAIM TO CHECK … check it against the code as it stands
    now, first". Round 2 of the incident received round 1's "I'm now waiting for the full
    Configuration.Tests suite… I'll report back once it completes."
    under that header and did exactly
    as instructed: it re-ran the suite, in the background, for twelve minutes, and ended the same way.
    A note now travels with a last_round_note_kind, and an unfinished_check arrives under a header
    saying its result no longer exists and pointing the round at redoing the check narrowed, reading
    config.json first. Notes written before that field existed carry no kind and still arrive as
    claims — a real blocker must — so the same warning is merged into the default frame as well.
    --reset-failed keeps the kind with the note it labels, because a note that loses its label is
    silently promoted back to a claim.
  • Tempa's own cleanup notice is no longer quoted back as an epic's blocker. _log_reclaimed
    appends its message into the session log after the agent's last word, so on any session that
    left processes running it is the tail — and the log-tail fallback would hand it back as the
    session's explanation, telling a human in the Halted panel that their epic was blocked on "Turn
    off Terminate leftover processes in Settings → Runs". The existing [Done] trim could not catch
    it, since that only fires when [Done] is the last line. The message is now built from the same
    constant the outcome layer matches, so the two cannot drift apart.
  • A stalled round that left nothing quotable now still explains itself. blocked_reason was
    assigned unconditionally on that path, so a round with no captured prose and an unreadable log
    stored "" — and both the dashboard's Halted panel and tempa status render their block only
    when that field is truthy, so the epic became a bare red ✗ with no explanation and, since the
    retry hint rides on the same string, no way out anywhere on the page.
  • An agent's closing words no longer carry its tool-call JSON. One assistant event can carry a
    text block and a tool_use block, joined with a newline, and the capture only rejected a readable
    that started with [. So prose plus a run_in_background call was stored whole and could reach
    blocked_reason, which is the entire content of the Halted panel. Lines are now filtered
    individually.

Changed

  • The prompts tell a session to narrow a long run, not to background it or ask for more time.
    The system prompt has forbidden ending a turn to wait since 0.6.6, and the incident happened
    anyway in a session that complied with it: it asked for the foreground with a 600000 ms timeout
    and was answered "Command did not complete within its 600s timeout and was moved to the
    background"; its later, compliant until [ -s … ]; do sleep 5; done foreground wait was
    backgrounded too. An instruction the harness converts into the forbidden action is not an
    instruction. The implementation and QA prompts now carry a rule naming a reachable move — narrow
    the run until it fits — plus "write output to a file rather than piping it through tail", and
    the system prompt's contradictory "raise the timeout" sentence is corrected to match.
  • A feature is recorded before the wider regression run, not after it. The incident round
    finished its entire allotted batch of three features and verified all of them, then went to run a
    full suite before updating config.json — so the round was scored as having achieved nothing.
    The rule now hangs the record off finishing and checking each feature, ahead of any wider run. The
    trade is deliberate and stated: a done a later suite disproves goes back through QA as
    require_fixing, one extra round, which is the right direction because an unrecorded feature is
    invisible while a wrongly-recorded one is correctable.
  • Tempa warns when a backend's background-work ceiling disagrees with backend_background_wait_sec.
    That setting reaches the CLI as an environment default only, under a variable name the vendor owns,
    so if the name is ever renamed the setting silently stops doing anything and every message telling
    you to raise it becomes advice about a disconnected knob. Nothing else in the runner could notice.
    It has something to say already: every ceiling message in the workspace this came from reported
    Claude Code's own 600s default rather than Tempa's configured value.
  • The background-terminated marker is now matched against the raw output line rather than the
    narrowed failure-marker text, so it is still recognised when the CLI reports it inside a result
    event with exit code 0.
  • max_session_run is documented for what it is — a backstop against a run of consecutive
    zero-progress sessions, not a lifetime session cap. It was never reached in the incident
    workspace: one epic has 20 session logs on disk and a stored total_run of 2.

v0.8.0

Choose a tag to compare

@daus95 daus95 released this 18 Aug 07:42
4cf1598

0.8.0 finishes what 0.7.0 started. That release gave a session somewhere to park a feature it can't finish without a human choice. This one gives you somewhere to answer it.

Answer a parked decision from the dashboard

A deferred epic's card on the Implementation page now shows each blocked feature's question and the session's own recommendation as their own elements, with an Answer… button beside them. It opens a dialog with three choices — follow the recommendation, write your own answer, or drop the feature — and Save does everything the manual edit did. The epic returns to the queue on the next poll exactly as before.

Until now the callout told you to open config.json, find the right entry among the epics, and type into a blocked_answer field. On a real plan that is a 300KB file with a running agent writing to it — a bad thing to ask anyone to edit by hand and an easy thing to break. Answering by hand still works, and is still what tempa status, the halt log and the decision email describe, since there's no button in any of those.

Your answer can't be lost to a write race

config.json has several uncoordinated writers — the runner's session threads, and the spawned agent, which is told to edit its own epic's entry directly. A field written from a third process can be overwritten by whichever of them next saves from a copy it read beforehand. That failure is silent, and it's the worst one available here: you believe you decided, and the epic never comes back.

So answering is written down twice. The decision is recorded first under .tempa/decisions/ — one file per answered feature, one writer, one reader, the same shape the graceful-stop sentinel already uses — and only then written into config.json, through a new cross-process lock (.tempa/config.lock) that re-reads the file inside the lock and touches nothing but that one field. The runner re-applies the recorded answer on every poll until the epic has actually moved, so an overwrite costs a poll interval instead of the decision, and the record is retired once it has been acted on.

Fixed

Dropping a blocked feature no longer strands its epic in deferred forever. The instructions have always offered dropping a feature outright — set its status to done and say why in blocked_answer — but a dropped feature is no longer blocked, so nothing recognised it as answered, and deferred is skipped by both the scheduler and the QA gate. An epic now returns to the queue as soon as nothing is left waiting on you, whether that came about by answering or by dropping, hand edits included.

Full changelog: https://github.com/daus95/tempa/blob/v0.8.0/CHANGELOG.md

v0.7.0

Choose a tag to compare

@daus95 daus95 released this 17 Aug 22:21
d88c678

What 0.7.0 is about: what happens when a session cannot finish a feature and is right about it.

Until now the runner had no state for that answer. Feature statuses were done/pending/require_fixing, and the only sanctioned "I'm stuck" channel was blocked_by_epic, which by construction can only point at another epic in the same plan. A session that had correctly worked out that a human must decide could only leave the feature require_fixing and argue its case in prose — which the no-forward-progress guard reads as a round that achieved nothing. Two of those and the epic was failed and the whole runner stopped.

Seen live: one unanswerable feature in an otherwise-finished epic halted a run with 49 features across 7 later epics, none of them related to the question, left unbuilt overnight.

Added

  • A feature can be parked on a decision only you can make, without stopping the run. A session may set a feature's status to blocked with a blocked_question and a blocked_recommendation — for the cases where someone has to choose: a spec naming a feature the product no longer wants, a QA report whose own recommended fix is "implement this or explicitly descope it", a migration whose blast radius needs signing off. The epic keeps building its other features; only once the blocked ones are all that's left does it go deferred, which never stops the runner, isn't picked up by the scheduler, and doesn't park later epics behind it at the QA gate. Answer by writing into blocked_answer — the next poll requeues the epic and hands your decision to the session that takes it, told to implement it as given. A new implementation_decision_required email alert, tempa status and the dashboard card all carry the question and the recommendation, since "yes, do that" is the common reply.

    The prompt rule is where abuse is handled: it requires having actually attempted the feature, requires naming the options and recommending one, and explicitly rules out "it is large", "I am running out of budget" and "it looks risky" — those describe work, not a decision.

Changed

  • A fix session is told when its QA report has gone out of date. An epic is only re-QA'd once it's done again, so one finding nobody can close kept it in require_fixing and the same report was handed to every following round — verbatim, still labelled "All ❌ and ⚠️ findings MUST be fixed" — while the features around it were fixed and other epics shipped. Each QA round now stamps the feature count its verdict was formed against, and a later session is told how many features that report never saw and asked to re-verify each finding against the current code first.
  • A stalled round's own conclusion is carried into the next one, as a claim to check. One epic spent four consecutive rounds re-deriving the same conclusion, each ending in a longer restatement than the last — and --reset-failed then dropped the session id too, so the retry began with less than the round before it. Framed as a claim rather than a finding deliberately: on that same epic the first three rounds' stated blockers were wrong, disproved by the fourth round's own investigation. The prompt names the two acceptable outcomes — disprove it and get on with the work, or confirm it and record it so it reaches a human.
  • blocked_reason quotes what the agent said, not what its tools printed. It was the last six lines of the session log, which cannot be separated back into agent prose and command output. One epic's Halted panel opened with a psql table header and (0 rows); another's with an Edit tool's success message.

Fixed

  • An epic is no longer failed a round early because the session wrote the stall counter itself. no_progress_rounds was the one runner counter missing from the fields snapshotted and restored around each session, so an agent rewriting its own config.json entry could carry it along and the runner would increment the agent's number instead of its own.
  • A circular epic dependency is caught as soon as it's declared, and says what to change. Previously only noticed once Tempa had been asked to reorder the pair in both directions — a round and a pointless reorder late. It now also refuses when the named epic has itself recorded blocked_by_epic pointing back, and both branches name the three things that actually resolve a cycle. The plan and plan-review prompts rule cycles out where they're written, too: "bidirectional with EPIC-02" is a cycle, not a dependency note.
  • A principle conflict has somewhere to go. The Architecture Principles block told every session to "report the conflict explicitly and stop" — contradicting the autonomous system prompt's "FORBIDDEN: … stopping after analysis", and pointing nowhere.

Full Changelog: https://github.com/daus95/tempa/blob/v0.7.0/CHANGELOG.md

v0.6.12

Choose a tag to compare

@daus95 daus95 released this 17 Aug 11:30
41994e1

Fixes the QA convergence guard v0.6.11 shipped a better message for, but that could still trip on evidence it had already used.

Fixed

  • The QA loop guard no longer re-strikes a regression it already counted. Its pattern rules scanned an epic's whole QA history for a feature that failed, was fixed, was re-verified, and is failing again — so once a feature regressed once, every later round it stayed failing re-found that same old gap and scored another strike. An epic then tripped on "2 rounds in a row showing it" when only the first round actually showed anything, turning qa_loop_strikes into "one regression plus N-1 ordinary consecutive failures" and spending exactly the tolerance that limit exists to provide.

    Seen on a live epic: a feature failed round 1, passed rounds 2-3, regressed in round 4 (a genuine strike), then round 5 flagged it again for an unrelated new defect that broke nothing — and was struck a second time for round 4's already-counted gap. A regression now counts only when the re-verification is the round immediately before it, and a round identical to the one right before it is no longer read as the epic "coming back around" to an earlier state. Genuine oscillation still trips on the second strike as before, and standing still is still bounded by max_qa_fail_rounds.

Changed

  • Fix sessions now see the epic's earlier QA reports, not just the newest one. A session told only what the latest round found can reroute control flow around a code path an earlier round already had to fix, bringing that finding straight back — the single most common way an epic starts cycling through QA in the first place. The older reports are now listed (oldest first) as settled findings to check new changes against and to leave a guarding test behind for, explicitly not as work to redo.
  • The convergence halt's diagnosis hint now also names a spec that never settled the question, not only a contradiction between two reports. Its first branch used to ask only whether the two rounds "ask for opposite things" — which doesn't fit the common case where each round is internally consistent and simply turns on something the spec never addressed. Both need the same response: a ruling written into the epic's spec or Architecture Principles.

Full changelog: https://github.com/daus95/tempa/blob/v0.6.12/CHANGELOG.md

v0.6.11

Choose a tag to compare

@daus95 daus95 released this 17 Aug 03:49
8d42dfd

Closes the loop on what a user actually does when the QA convergence guard halts an epic — the question v0.6.9 and v0.6.10 left half-answered.

Changed

  • The halt no longer tells you to "fix the underlying conflict between them". That wording named one cause as though it were the only one. The guard trips on a pattern — a feature that failed QA, was fixed, was re-verified, and is failing again — and that pattern has several causes wanting opposite responses. The EPIC-13 halt that started this whole line of work was nitpick churn, not a contradiction, so the old message actively sent its reader looking for something that wasn't there.

    The halt now describes how to tell them apart: compare the last two QA reports for the features named, then

    What they show What it is What to do
    They ask for opposite things The spec is ambiguous or contradicts itself Your ruling — write it into the epic's spec or Architecture Principles
    One reports something genuinely broken An ordinary regression Retry; the next fix session sees both reports
    Each round flags different, minor items Nothing is regressing Raise Features per Session / QA Loop Strikes

    It rides in blocked_reason, so it appears in the dashboard's ⚠ Halted panel and in tempa status, not only in the log. "Fixing what QA reports is undoing earlier work" was also softened to "may be" — the guard detects a pattern; it does not know that.

Added

  • Each epic's card now has a "Spec" button, opening that epic's own specification in the same editor the Specification section uses. It was previously unreachable from the dashboard entirely: sources.epics is a sibling of the PRD folder that section's tree is rooted at, so nothing under it could be browsed to. That left the file QA grades an epic against — and the one to correct when QA rounds genuinely contradict each other — as the one file you had to leave the dashboard to read.

    It is addressed by epic label rather than by path: the file is re-resolved server-side on both read and save, so the only writable target is a spec that already exists for a real epic. EPIC-1 will not open EPIC-13's spec, and two files claiming one epic returns an error rather than a guess.

Full changelog: https://github.com/daus95/tempa/blob/v0.6.11/CHANGELOG.md

v0.6.10

Choose a tag to compare

@daus95 daus95 released this 17 Aug 03:21
b62c83c

The follow-up to v0.6.9's QA gate fix: making a halted epic explain itself, and putting the guard that halts it under your control.

Fixed

  • A halted epic never said how to get it moving again. When a guard gives up on an epic, the dashboard's ⚠ Halted panel shows that epic's blocked_reason and nothing else — but the remediation was only ever written to the log line beside it. So the one place a user actually reads about the halt didn't mention that clicking Continue Implementation recovers it, even though that button has always run tempa implement --reset-failed itself before every implementation pass. Every guard now appends the way out to blocked_reason, dashboard route first, so it shows up both on the epic card and in tempa status.
  • Three of the five ways an epic can fail wrote no blocked_reason at all. Hitting max_session_run, QA hitting the same limit without ever passing, and an implementation session exiting non-zero all marked the epic failed and left it at that — on the Status tab that was a bare red ✗ with no cause and no next step anywhere, the explanation buried in the process log. All three now record what happened alongside how to retry. The QA-limit one says explicitly that the epic was marked failed rather than passed because it has never been verified — an epic that fails at the QA limit otherwise looks a lot like one that passed.
  • Saving Settings from a dashboard tab left open across an upgrade no longer fails, or silently resets a run-limit field the payload doesn't mention. Such a field is now left exactly as it is on disk; a blank value is still a deliberate answer and behaves as before.

Added

  • qa_loop_strikes and max_qa_fail_rounds now have Settings controls (Settings → Runs → Run Limits, as "QA Loop Strikes" and "Max QA Fail Rounds"). These two decide when Tempa gives up on an epic and halts the run, which makes them the knobs you are most likely to want to change right after being stopped by them — and they were the only run limits that could only be changed by hand-editing config.json.

Full changelog: https://github.com/daus95/tempa/blob/v0.6.10/CHANGELOG.md

v0.6.9

Choose a tag to compare

@daus95 daus95 released this 17 Aug 02:50
b0cbd01

Fixes a QA gate that could never let a detailed epic converge.

An epic would cycle through QA and eventually be halted as "cycling through QA rather than converging" while the code under review was fine. Observed on a real 7-feature epic: 5 QA rounds, ~2.5 hours, never converged — and its final report carried zero ❌ items, saying in as many words that none of its findings were regressions.

Fixed

  • ⚠️ was treated exactly like ❌. Anything a reviewer merely wanted improved marked its feature require_fixing, and against a spec with dozens of literal "How to test" bullets an LLM reviewer will always find one whose exact phrasing no test is named after — so each round failed a different subset of features on fresh nitpicks. QA now grades at three levels and only two block: ❌ (not implemented, or fails when run), ⚠️ (implemented, but observable behaviour or contract differs from the spec — the agent must state what goes wrong at run time and for whom), and 📝 advisory (correct and verified, but more coverage or a better-named test would be nice). Advisory notes get their own report section and never fail a feature.
  • Rounds now build on each other. From round 2 on, the QA prompt carries the previous round's report and requires the agent to re-verify its ❌/⚠️ items first — stating which findings are repeats and whether an earlier fix was undone — treat its 📝 notes as settled, and only then look for genuinely new defects. A report is written on every round, a passing one included, so advisory notes survive.
  • A QA agent can no longer halt the run by editing the runner's bookkeeping. config.json is shared with the spawned agent, and one QA session appended its own qa_history entry for the round it was still working on — leaving two identical rounds pointing at one report file, exactly the fingerprint the loop guard reads as an epic going in circles. qa_history, qa_loop_strikes, blocked_reason, total_run and qa_total_run are snapshotted before every session and restored afterwards, and one report file is now one round.
  • A round that flagged no features is no longer read as proof every feature was re-verified. Both loop-guard pattern rules key off a feature being absent from a round in between; an empty round made every feature look absent, so one round of missing bookkeeping could read as a wholesale regression. The max_qa_fail_rounds backstop still bounds those rounds.

The loop guard itself was deliberately not weakened — replaying the real history shows it behaving exactly as designed. The premise it reasoned from was false, not the rule.

Full changelog: https://github.com/daus95/tempa/blob/v0.6.9/CHANGELOG.md