Split advanced liveness behavior from reliability PR

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-04-30 15:20:19 -05:00
1 parent 5684e17b79
commit c66bcd38f8
5 files changed
+25 -64

No files matched your search

@@ -2180,8 +2180,8 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
expect(wakeups).toHaveLength(1);
});
it("does not treat a productive terminal run as healthy when in-progress work has no live path", async () => {
const { companyId, agentId, issueId } = await seedStrandedIssueFixture({
it("records productive continuation instead of recovery when the latest automatic continuation succeeded", async () => {
const { agentId, issueId, runId } = await seedStrandedIssueFixture({
status: "in_progress",
runStatus: "succeeded",
retryReason: "issue_continuation_needed",
@@ -2189,41 +2189,27 @@ describeEmbeddedPostgres("heartbeat orphaned process recovery", () => {
});
const heartbeat = heartbeatService(db);
const sourceIssue = await db.select().from(issues).where(eq(issues.id, issueId)).then((rows) => rows[0] ?? null);
expect(sourceIssue).toMatchObject({
status: "in_progress",
assigneeAgentId: agentId,
assigneeUserId: null,
executionRunId: null,
});
const activeRuns = await db
.select()
.from(heartbeatRuns)
.where(and(eq(heartbeatRuns.companyId, companyId), inArray(heartbeatRuns.status, ["queued", "running"])));
expect(activeRuns).toHaveLength(0);
const liveWakeups = await db
.select()
.from(agentWakeupRequests)
.where(and(eq(agentWakeupRequests.companyId, companyId), inArray(agentWakeupRequests.status, ["queued", "deferred_issue_execution"])));
expect(liveWakeups).toHaveLength(0);
const result = await heartbeat.reconcileStrandedAssignedIssues();
expect(result.productiveContinuationObserved).toBe(0);
expect(result.continuationRequeued + result.escalated).toBe(1);
expect(result.issueIds).toEqual([issueId]);
expect(result.continuationRequeued).toBe(0);
expect(result.productiveContinuationObserved).toBe(1);
expect(result.successfulContinuationObserved).toBe(0);
expect(result.escalated).toBe(0);
expect(result.issueIds).toEqual([]);
const issue = await db.select().from(issues).where(eq(issues.id, issueId)).then((rows) => rows[0] ?? null);
expect(issue?.status).toBe("in_progress");
const comments = await db.select().from(issueComments).where(eq(issueComments.issueId, issueId));
const recoveryIssues = await db
expect(comments).toHaveLength(0);
const runs = await db
.select()
.from(issues)
.where(and(eq(issues.companyId, companyId), eq(issues.originKind, "stranded_issue_recovery")));
const followupWakeups = await db
.select()
.from(agentWakeupRequests)
.where(and(eq(agentWakeupRequests.companyId, companyId), eq(agentWakeupRequests.reason, "issue_continuation_needed")));
expect(comments.length + recoveryIssues.length + followupWakeups.length).toBeGreaterThan(0);
.from(heartbeatRuns)
.where(eq(heartbeatRuns.agentId, agentId));
expect(runs.map((row) => row.id)).toEqual([runId]);
const wakeups = await db.select().from(agentWakeupRequests).where(eq(agentWakeupRequests.agentId, agentId));
expect(wakeups).toHaveLength(1);
});
it("does not reconcile user-assigned work through the agent stranded-work recovery path", async () => {
+1 -23
View File
@@ -106,28 +106,6 @@ describe("run liveness continuations", () => {
expect(decision.nextAttempt).toBe(2);
});
it("enqueues advanced terminal runs so progress is not mistaken for a live path", () => {
const decision = decideRunLivenessContinuation({
run: run(),
issue: issue(),
agent: agent(),
livenessState: "advanced",
livenessReason: "Run produced concrete action evidence: created an issue comment",
nextAction: "Resume the implementation from the remaining acceptance criteria.",
budgetBlocked: false,
idempotentWakeExists: false,
});
expect(decision.kind).toBe("enqueue");
if (decision.kind !== "enqueue") return;
expect(decision.payload).toMatchObject({
issueId,
sourceRunId: runId,
livenessState: "advanced",
instruction: "Resume the implementation from the remaining acceptance criteria.",
});
});
it("does not enqueue a third continuation and returns an exhaustion comment", () => {
const decision = decideRunLivenessContinuation({
run: run({ continuationAttempt: 2 }),
@@ -148,7 +126,7 @@ describe("run liveness continuations", () => {
it("skips non-actionable and guarded issues", () => {
const guardedCases = [
{ livenessState: "needs_followup" as const },
{ livenessState: "advanced" as const },
{ issue: issue({ status: "done" }) },
{ issue: issue({ assigneeAgentId: "other-agent" }) },
{ issue: issue({ executionState: { status: "pending" } }) },
+1 -1
View File
@@ -2805,7 +2805,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
async function handleRunLivenessContinuation(run: typeof heartbeatRuns.$inferSelect) {
const livenessState = run.livenessState as RunLivenessState | null;
if (livenessState !== "plan_only" && livenessState !== "empty_response" && livenessState !== "advanced") return;
if (livenessState !== "plan_only" && livenessState !== "empty_response") return;
const context = parseObject(run.contextSnapshot);
const issueId = readNonEmptyString(context.issueId);
@@ -7,7 +7,7 @@ import { RECOVERY_REASON_KINDS } from "./origins.js";
export const RUN_LIVENESS_CONTINUATION_REASON = RECOVERY_REASON_KINDS.runLivenessContinuation;
export const DEFAULT_MAX_LIVENESS_CONTINUATION_ATTEMPTS = 2;
const ACTIONABLE_LIVENESS_STATES = new Set<RunLivenessState>(["plan_only", "empty_response", "advanced"]);
const ACTIONABLE_LIVENESS_STATES = new Set<RunLivenessState>(["plan_only", "empty_response"]);
const CONTINUATION_ACTIVE_ISSUE_STATUSES = new Set(["todo", "in_progress"]);
// A prior adapter error should not permanently suppress bounded liveness
// continuations; the max-attempt/idempotency guards prevent unbounded retries.
@@ -127,10 +127,6 @@ export function decideRunLivenessContinuation(input: {
if (budgetBlocked) {
return { kind: "skip", reason: "budget hard stop blocks continuation" };
}
if (livenessState === "advanced" && !nextAction) {
return { kind: "skip", reason: "advanced run has no concrete continuation instruction" };
}
const currentAttempt = readContinuationAttempt(run.continuationAttempt);
if (currentAttempt >= maxAttempts) {
return {
+3 -2
View File
@@ -189,12 +189,13 @@ function isUnsuccessfulTerminalIssueRun(latestRun: LatestIssueRun) {
}
function isSuccessfulInProgressContinuationRun(latestRun: LatestIssueRun) {
return latestRun?.status === "succeeded" && latestRun.livenessState !== "advanced";
return latestRun?.status === "succeeded";
}
function isProductiveContinuationRun(latestRun: LatestIssueRun) {
return latestRun?.status === "succeeded" &&
(latestRun.livenessState === "completed" ||
(latestRun.livenessState === "advanced" ||
latestRun.livenessState === "completed" ||
latestRun.livenessState === "blocked" ||
latestRun.livenessState === "needs_followup");
}