fix(native): wake parents for revised child completions

Scope ordinary child completion wake identity to the durable status decision. Keep watchdog deduplication and replay behavior. Record the merged guidance work and the bounded follow-up plan.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-10-06 12:47:57 -05:00
1 parent 0fe47882cf
commit fa36cc8e9d
4 files changed
+349 -93

No files matched your search

@@ -1,17 +1,25 @@
# Stock harness, with Paperclip: working checklist
Created: 2026-10-02. Status: item 1 merged for native Codex app-server in
[PR #14920](https://github.com/paperclipai/paperclip/pull/14920). Item 2's default
hire manual is reduced to identity only, and common legacy startup/resume
instructions are in [PR #14948](https://github.com/paperclipai/paperclip/pull/14948).
GitHub live qualification retains earlier document-delivery failures; the latest
focused OpenCode comparison passes both cases in both variants but still exposes
deficient original-case handoff links. PR #14948 remains draft. The original
[live report](2026-10-02-stock-harness-live-comparison.md), completed
[skill repair](2026-10-02-legacy-document-skill-repair.md) and latest
[stock-selection/link comparison](2026-10-02-opencode-skill-routing-link-qualification.md)
retain their separate sources and limitations. Additional carriers and fixed
native instructions remain open; hiring templates are being reduced separately.
Created: 2026-10-02. Status refreshed locally: 2026-10-06.
Native Codex base preservation (#14920), the tiny default manual/common legacy
prompts (#14948), general hiring templates (#14985), native completion tool
guidance (#14961), evaluator accounting repairs (#15007), and native completion
constraint/final-answer corrections (#15151) are merged. Dotta confirmed the
latest merge; GitHub records #15151 merged on 2026-10-05. The detailed sections
below retain historical stages and failures; this status supersedes their old
draft/pending descriptions. The saved master checklist had lagged behind the
implementation and evidence notes and is reconciled here. Native-tool measurement
(#15218) and the smaller planning skills (#15296) are also merged; the
2026-10-06 entry below records their scope and the next repair.
Remaining work: deferred legacy carriers (2.2), uncommon native fixed-prompt
procedures beyond the completed completion slice (2.3), specialized agent
contracts (3.4), repository context (4), broader runtime bookkeeping (5), and
the full harness/configuration audit (6). Completed evals qualify their bounded
cases, not general coding quality or every configuration. The prepared final
#15151 PR body/eval summary was not published before the human merged it;
publication remains separate from implementation completion.
Goal: keep the agent's stock harness behavior and add only what it needs to work
with Paperclip. Apply this across legacy adapters, the new Runner, and their
@@ -26,7 +34,7 @@ For every change, record its executable test/eval coverage before continuing.
Distinguish coverage setup, deterministic results, and measured live results;
configured cells alone do not qualify behavior.
**Current item: 2 — reduce the default operating manual and shared prompt layers.**
**Current item: repair and qualify the native delegation handoff before further procedure reduction.**
## Agreed direction and boundaries
@@ -106,8 +114,10 @@ This replaces 602 words with eight. The shared loader supplies this default
across instruction-bundle-capable adapters for non-CEO hires without explicit
instructions. Existing saved bundles retain their content; CEO, first-agent,
and role/team templates remain separate work under item 3. The common
prompt/wake reduction is complete locally below; additional carriers, native
instructions, and skill/reference corrections remain open.
prompt/wake reduction and legacy delivery repairs are merged in #14948;
additional carriers and uncommon native procedures remain open. Native
completion documentation and constraint/final-answer corrections are merged
separately in #14961 and #15151.
Verification: the existing agent-skills route suite passed all 54 tests,
including default creation, custom bundles, and CEO/first-agent paths. The
@@ -134,6 +144,123 @@ and existing-test update.
native tool-documentation slice on 2026-10-02 in a separate worktree. Native
`paperclip_finish`/`paperclip_block` guidance must not leak into legacy
completion paths, which use the operational skill and API.
- [x] **2.3 completion slice.** Improve native finish/block documentation
(#14961), consolidate repeated completion constraints, and preserve useful
final replies, blocker explanations and working document links (#15151).
Native Codex, ACPX Claude and OpenCode have bounded live outcome coverage;
scripted start/resume/continuation and compatibility checks remain distinct
from live resume qualification.
- [ ] **2.3 remaining procedures.** Review the fixed hiring, dependencies and
connection guidance; improve discoverable tool documentation and measure any
further reduction. #15151 intentionally did not change the fixed prompt.
Hiring/dependency relocation failed both corrected and readiness comparisons.
#15218 now retains measurement and eval coverage with production unchanged;
see [the comparison report](2026-10-05-native-procedure-guidance.md).
Connection procedures remain a separate follow-up.
- [x] **2.3 full-catalog measurement.** PR #15218 measures all 39 supplied
native tools and schemas with fixed instructions at start/resume/continuation.
The rejected corrected candidate reduced the normalized standing projection by 460 bytes
(48,781 to 48,321 in the paid context; 49,200 to 48,740 after master integration),
not a token/cost or upstream truncation claim. The rejected readiness repair
restored explicit delegation/review guidance and saved only 125 bytes.
Its larger unqualified controller repair increased the full projection.
Final #15218 restores production to baseline: 49,200 bytes, identical across
all 36 normalized components of nine scripted deliveries and the MCP catalog.
- [ ] **2.3 hiring/dependency behavior qualification.** The original six-pair
campaign exposed three new overall failures, including Claude omitting
dependency recording and finalization. Preserve original grades and control
bugs. Corrected v7 candidate and a baseline with matching credential/budget
repairs completed: baseline 5/6 PASS, candidate 3/6 PASS; two new failures,
zero new passes, three unchanged passes and one unchanged failure. Codex's
parent remained blocked after the replacement deliverable. OpenCode hiring
exceeded the deadline before the revised delivery settled. Both OpenCode
delegation variants lacked parent review of the final child revision. Two
interrupted candidate cells had one bounded recovery each; incomplete
original accounting is retained. No rerolls of these completed source pairs.
After the human requested readiness fixes, candidate
`aba7ec219b588de02cf323b4ebca559a105e6d01` restores assigned-worker revisions
and latest-child review, returns actual dependency readiness, and separates
cancelled dependencies from tasks that can complete. Its 23 database-backed
tool tests and fresh 5/5 review pass. New candidate campaign `37348723829`
and integrated baseline `37348764875` completed with unchanged oracles and
bounds: candidate 3 PASS / 3 FAIL; baseline 3 PASS / 2 FAIL / one setup cell
without a behavioral grade. The five comparable pairs have two new failures,
two new passes and one unchanged pass; one pair remains uncomparable. The
new failures include parent completion before the latest child revision and
OpenCode credential persistence. The Codex candidate also lacks parent
continuation. Keep this item open. The final #15218 excludes the production
change, retains every failure, and adds measurement and eval coverage only.
Unqualified runtime fixes are preserved locally for separate follow-up.
### Remaining fixed-prompt audit — 2026-10-05
Read-only inspection verified the prompt, semantic catalog, shared connection
guidance, production tool authority and payload-measurement file against their
exact blobs on merged master `a386a599983519eb1d399f8b770bfccdb2a74762`.
The fixed prompt is 262 whitespace-separated words / 1,713 UTF-8 bytes: 34
general context, 123 hiring/delegation/dependencies, 98 connections and seven
finishing words. These are source-text counts, not provider token/billing data.
The hiring recipe still mandates `search_api`/`call_api`, although the available
`hire_agent` tool already creates persistent native teammates and documents
runtime inheritance/reuse. Hiring and API fallback tools share the API-tool
availability gate. Update the discovery/selection guidance to prefer an
available dedicated tool and keep configuration-specific fallback in its own
documentation; provider helper threads must remain distinct from company hires.
`create_task` documents creation but omits the assigned-worker/review boundary.
`set_dependencies` describes replacement only; preserving existing blocker IDs
and releasing the workspace are still carried by the fixed prompt. Improve
these tool contracts before removing the corresponding prompt procedure.
Connection tools already document discovery, recorded provider choices, setup
cards and yielding. Keep a small discovery trigger for explicit setup requests;
put state-specific retry/decline/continuation instructions in tool descriptions
and actual result guidance. These are reduction proposals, not implemented or
behaviorally qualified changes.
Measure the complete delivered prompt, constraints and granted tool descriptions
together. The existing scripted capture selects only core semantic tools, so it
omits these optional hiring/dependency tools and the connection tools. Moving
words into descriptions alone does not demonstrate lower total instruction load.
Keep prompt revision/digest and incompatible-session checks explicit.
Candidate coverage includes the existing Product E2E `hire-reuse`,
`delegate-feedback`, service approval/decline and connection-routing stories.
Those stories cover Codex/Claude; corresponding OpenCode qualification and exact
blocker-preservation/workspace-yield assertions still need review. Some decline
fixtures explicitly restate the no-retry rule, so they cannot alone measure its
discovery from shipped guidance. Previous completion-only passes do not qualify
these procedures. Prefer hiring as the first bounded slice, then matched
before/after outcomes with common fixture/model/auth/permission/budget controls.
### Follow-up 2.3 merged completion slice — 2026-10-05
#15151 merged as `a386a599983519eb1d399f8b770bfccdb2a74762` from final source
`16ef1a5744b23bb043a8b286e6288921da1de98f`. The shortened completion contract
now retains an explicit blocker cause/owner/action and a canonical clickable
document handoff. Runtime feedback, OpenCode acceptance/settlement and proxy
ordering corrections preserve the final provider response rather than treating
an accepted terminal tool call as sufficient delivery.
The six scoped corrective cases pass: four Codex/Claude cases measured at
`3a7349ddc60142283c394121882288f2db07b215`, and two OpenCode cases measured at
the final source. The intervening production delta is confined to OpenCode;
the shared instructions, server feedback and UI paths are byte-identical.
These are explicitly mixed-source observations, not six live runs on the final
head. Document cases open the actual saved revision and original marker from
the final reply; blocker cases retain the missing-access reason, exact owner,
unblock action and task-wide scope. Final-head CI is green and fresh review is
5/5 with all review threads resolved.
Original paired grades and every corrective failure remain preserved. Passing
totals had concealed weak blocker explanations and missing document links;
the subsequent oracle explicitly checks those behaviors. Single trials do not
prove broad equivalence, cognitive skill consumption, general coding quality,
live resume behavior, or speed/cost trends. See the
[constraint plan](2026-10-03-native-completion-consolidation.md) and
[final-answer correction history](2026-10-04-native-completion-answer-fix.md)
for the preserved setup and behavior failures; their initial hold statements
are historical and superseded by this merged status.
### Follow-up 2.1 implementation and verification
@@ -328,6 +455,10 @@ isolation is approved and should remain.
## 5. Let the runtime own bookkeeping
- [x] **Native completion delivery slice.** Persist and present meaningful final
answers, saved document links and blocker details; settle accepted/rejected
OpenCode completion feedback correctly. Merged in #15151. This does not finish
the wider checkout, waiting, recovery and legacy ownership audit below.
- [ ] Map which runtime owns checkout, status transitions, completion comments,
artifacts, waiting/review states, and recovery; identify manual duplicates.
- [ ] Keep one valid completion path per runtime and preserve meaningful user
@@ -369,6 +500,10 @@ does not apply.
## Verification and evals — apply to each item
- [x] Repair strict retained-run accounting in #15007 and add bounded native
completion/final-answer coverage for #14961/#15151. Keep original failures;
ACPX host/provider action attribution and hiring source-read coverage remain
explicitly uncomparable where no authoritative mapping/receipt exists.
- [x] Map existing coverage for all implemented changes before adding cases;
see the SH-1–SH-3 map below and [doc/evals.md](../evals.md).
Keep Runner protocol evals and Product E2E evals distinct.
@@ -462,6 +597,7 @@ will address them. Record intentional behavior explicitly rather than as a bug.
| Date | Decision / outcome | Evidence / follow-up |
| --- | --- | --- |
| 2026-10-05 | Dotta merged #15151; reconcile the saved roadmap with completed implementation/evidence notes. | Native completion slice complete; general hiring reductions and common legacy prompts also merged. 2.2, remaining fixed native procedures, 3.4 and the broader 4–6 audits stay open. Preserve old failures and bounded qualification limits; no new model run or public report publication in this checklist update. |
| 2026-10-02 | Dotta approved preserving stock instructions, smaller defaults/templates, minimal coordination, runtime bookkeeping, and coverage across harnesses. | Implementation details to work through one item at a time. |
| 2026-10-02 | Paperclip-owned MCP isolation and configuration changes/session resets are acceptable. | Preserve Paperclip auth and assigned skills while fixing repository context. |
| 2026-10-02 | Dotta requested implementation and a PR for item 1. Native app-server paths now use additive developer instructions. | 139 targeted TypeScript tests and 91 Rust provider tests passed; repository typecheck/build passed. Remaining test/review results to record. |
@@ -489,3 +625,24 @@ will address them. Record intentional behavior explicitly rather than as a bug.
For each completed item, add the chosen behavior, changed paths, verification
results, remaining exceptions, and follow-ups here before checking it off.
### 2026-10-05 final PR #15218 scope
- [x] Preserve all three experimental cohorts and failed original grades.
- [x] Reject the unqualified procedure relocation; final production bytes match master a65ca0950834a85bb93bcc4b4042ecacdebfef53.
- [x] Keep complete native-tool measurement, explicit workflow coverage, selected-provider credentials and budget guards.
- [x] Full local typecheck/build and 1,252 eval-support + 128 Node tests pass.
- [x] Final head 2e7cef78eef7cdfe02265e0dcb03e855b8e50bd8: all 47 CI jobs pass; one initial annotation-test timeout retained and one targeted retry passed. Fresh Greptile 5/5, three threads resolved, published body verified, both ready-transition security scans pass, MERGEABLE and no longer draft.
- [x] PR #15218 merged on 2026-10-06 at 01:03 UTC. The preceding approval hold is historical; the final merged scope is measurement and eval coverage with production unchanged.
- [ ] Section 2.3 procedure relocation remains unshipped; the trials do not justify it. Runtime follow-up preserved separately and unqualified.
### 2026-10-06 roadmap update
- [x] **#15218 merged:** complete native-tool measurement and explicit hiring/delegation eval coverage. The proposed production procedure relocation failed qualification and was excluded. Original failures remain recorded.
- [x] **#15296 merged:** shrink the runtime and bundled plan-to-tasks skills while retaining their installation keys and accepted-plan wiring. GitHub records the merge at 14:36 UTC. The detailed completed comparison report remains local pending its separate publication consent; merging the implementation does not publish that report.
- [ ] **Next bounded repair:** a reopened native child must notify its parent after its new completion, even when the first notification has already been consumed. Replaying the same committed completion must not create another wake. Work is on `codex/delegation-handoff`, based on master `0fe47882cfcb12082035113c59ca96091c46ebfc`.
- [ ] Qualify that repair with a database-backed regression and a frozen baseline/candidate `delegate-feedback` comparison using the existing outcome oracle. Keep original grades, incomplete evidence and all actual runs.
- [ ] Revisit hiring/dependency instruction reduction only after the bounded behavior is qualified. Premature parent completion, discarded unfinished dependencies and broader latest-child review remain separate open behavior questions.
- [ ] Audit connection procedures separately, then return to the remaining 2.2 / 3.4 / 4–6 work.
@@ -0,0 +1,66 @@
# Native delegation handoff repair
Date: 2026-10-06. Owner: this Codex task. Branch: `codex/delegation-handoff`.
Starting master: `0fe47882cfcb12082035113c59ca96091c46ebfc`.
## Finish line
A reopened ordinary native child can complete again and deliver one fresh completion
notification to its parent. Replaying the same committed completion remains
idempotent. Prepare a reviewed, verified PR; merging is a separate human action.
## Scope
The native status committer currently deduplicates parent notifications by the
parent/child pair. A consumed notification can therefore suppress a later
completion of the same child. Scope the key to the durable completion decision
in both parent-notification branches, including a child that is also a blocker.
Keep existing readiness, company, ownership, workspace finalization and governance
gates. Do not change model prompts, tools, dependency replacement semantics or
the outcome oracle. General dependency-only wake reconciliation is separate. Exact `task_watchdog`
children keep their existing stable wake key to avoid repeated watchdog loops;
near-match origin names remain ordinary children. Related open #10559 and
#4507 concern legacy wake deduplication; #11179 concerns delivery during an
active parent run; #13044 separately proposes suppressing watchdog signals.
This change preserves their existing routing boundaries and changes only the
native committed-decision wake key.
Prior procedure experiments in #15218 exposed stalled and premature parent
outcomes but do not alone prove this mechanism caused every failure. The
production relocation and earlier controller repairs were not shipped. #15296
shipped the smaller planning skills; its report publication is separate.
## Verification contract
1. Reproduce the consumed-wake collision against unchanged master production
with the real database/control-plane conformance test. Cover parents with
and without an explicit dependency edge, the latest child summary, and
duplicate finalization.
2. Run relevant conformance checks, repository typecheck, tests and build.
Preserve failures and distinguish environment limitations.
3. Freeze candidate and baseline branches on this master with identical
Product E2E fixtures, graders, provider profiles and budgets. Run only
`everyday-workflows.runner-codex.local.delegate-feedback` and
`everyday-workflows.runner-acpx-claude.local.delegate-feedback` in each
variant: one attempt each, 12-minute per-cell deadline, at most 12 story
run records, 1,000-cent company and lead-agent hard stops, concurrency 2.
No reroll of a completed behavior failure. All provider continuations and
automatic recovery count in the actual run inventory.
4. Inspect original grades and retained chronology, including revised child
delivery, worker ownership, parent review and parent completion ordering.
A green workflow is not itself a behavioral pass. A single pair does not
establish general equivalence, causation or cost/speed trends.
5. Retain dated measurement/results in the private `paperclip-evals` archive
and existing campaign artifact storage. Keep a compact index here. Verify
current-head CI and review before a readiness claim.
## Current state
- [x] Record merged #15218 and #15296 in the working checklist.
- [x] Reproduce both relationship cases: second completion leaves only the
original wake against unchanged production.
- [x] Verify the scoped correction and replay behavior: all 20 database-backed
control-plane conformance tests pass, including exact watchdog and near-match
origin controls. Repository checks are still running.
- [ ] Complete frozen live comparison and retain original evidence.
- [ ] Complete PR review and CI.
@@ -1523,13 +1523,14 @@ describe("PaperclipControlPlanePort conformance", () => {
});
});
it("coalesces a completed child dependency into one rich parent wake", async () => {
it.each(["child_dependency", "child"].flatMap((relationship) =>
["manual", "task_watchdog", "task_watchdog_product_bug"].map((originKind) => ({ relationship, originKind })),
))("preserves completion wake identity across reopen ($relationship, $originKind)", async ({ relationship, originKind }) => {
const hasDependency = relationship !== "child";
const identity = CONTROL_PLANE_CONFORMANCE_OPEN.identity;
const parentIssueId = "30000000-0000-4000-8000-000000000145";
const childIssueId = "30000000-0000-4000-8000-000000000146";
const localContractId = "31000000-0000-4000-8000-000000000146";
const runId = "32000000-0000-4000-8000-000000000146";
const runnerInstanceId = "33000000-0000-4000-8000-000000000146";
const parentIssueId = randomUUID();
const childIssueId = randomUUID();
const localContractId = randomUUID();
await db.insert(issues).values([
{
id: parentIssueId,
@@ -1544,12 +1545,13 @@ describe("PaperclipControlPlanePort conformance", () => {
companyId: identity.companyId,
parentId: parentIssueId,
title: "Implement the accepted plan",
originKind,
status: "in_progress",
assigneeAgentId: identity.agentId,
workMode: "standard",
},
]);
await db.insert(issueRelations).values({
if (hasDependency) await db.insert(issueRelations).values({
companyId: identity.companyId,
issueId: childIssueId,
relatedIssueId: parentIssueId,
@@ -1574,80 +1576,105 @@ describe("PaperclipControlPlanePort conformance", () => {
createdByActorType: "system",
createdByActorId: "test",
});
await db.insert(heartbeatRuns).values({
id: runId,
companyId: identity.companyId,
agentId: identity.agentId,
status: "running",
runtimeMode: "native",
nativeIssueId: childIssueId,
nativeSessionId: identity.sessionId,
runnerInstanceId,
completionContractId: localContractId,
completionContractSha256: "child-wake-contract",
contextSnapshot: { issueId: childIssueId },
});
const port = new PaperclipControlPlanePort(db, {
companyId: identity.companyId,
issueId: childIssueId,
runId,
agentId: identity.agentId,
sessionId: identity.sessionId,
completionContractId: localContractId,
completionContractSha256: "child-wake-contract",
sourceInstanceId: runnerInstanceId,
controlPlaneSourceInstanceId: "child-wake-control",
});
await port.openRun({
identity: { ...identity, issueId: childIssueId, runId },
backendKind: "mock",
sourceInstanceId: runnerInstanceId,
});
const result: PrpStructuredRunResult = {
...structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT),
summary: "Implemented the accepted plan and passed 44/44 tests.",
completionClaim: {
contractRevision: "child-wake-v1",
objectiveSatisfied: true,
criteria: [{ criterionId: "objective", status: "satisfied", evidenceRefs: [] }],
remainingWork: [],
},
verification: [{ commandOrCheck: "node --test", status: "passed" }],
attentionRequests: [],
};
await port.completeRun({
result,
terminal: CONTROL_PLANE_CONFORMANCE_TERMINAL,
callerResultId: "child-wake-result",
});
await finalizeNativeRun({ db, runId, workspaceFinalizeStatus: "succeeded" });
for (const revision of [1, 2]) {
const runId = randomUUID();
const runnerInstanceId = randomUUID();
if (revision === 2) {
// The original completion wake was already consumed. Feedback reopens
// the same child while its parent still needs the revised result.
await db.update(agentWakeupRequests).set({ status: "completed" })
.where(sql`${agentWakeupRequests.payload}->>'issueId' = ${parentIssueId}`);
await db.update(issues).set({ status: "blocked" }).where(eq(issues.id, parentIssueId));
await db.update(issues).set({ status: "in_progress", statusVersion: 2 })
.where(eq(issues.id, childIssueId));
}
await db.insert(heartbeatRuns).values({
id: runId,
companyId: identity.companyId,
agentId: identity.agentId,
status: "running",
runtimeMode: "native",
nativeIssueId: childIssueId,
nativeSessionId: identity.sessionId,
runnerInstanceId,
completionContractId: localContractId,
completionContractSha256: "child-wake-contract",
contextSnapshot: { issueId: childIssueId },
});
const port = new PaperclipControlPlanePort(db, {
companyId: identity.companyId,
issueId: childIssueId,
runId,
agentId: identity.agentId,
sessionId: identity.sessionId,
completionContractId: localContractId,
completionContractSha256: "child-wake-contract",
sourceInstanceId: runnerInstanceId,
controlPlaneSourceInstanceId: "child-wake-control",
});
await port.openRun({
identity: { ...identity, issueId: childIssueId, runId },
backendKind: "mock",
sourceInstanceId: runnerInstanceId,
});
const result: PrpStructuredRunResult = {
...structuredClone(CONTROL_PLANE_CONFORMANCE_RESULT),
summary: `Implemented revision ${revision} and passed 44/44 tests.`,
completionClaim: {
contractRevision: "child-wake-v1",
objectiveSatisfied: true,
criteria: [{ criterionId: "objective", status: "satisfied", evidenceRefs: [] }],
remainingWork: [],
},
verification: [{ commandOrCheck: "node --test", status: "passed" }],
attentionRequests: [],
};
await port.completeRun({
result,
terminal: CONTROL_PLANE_CONFORMANCE_TERMINAL,
callerResultId: "child-wake-result",
});
await finalizeNativeRun({ db, runId, workspaceFinalizeStatus: "succeeded" });
const [completedChild] = await db.select().from(issues).where(eq(issues.id, childIssueId));
expect(completedChild).toMatchObject({ status: "done", statusVersion: revision === 1 ? 1 : 3 });
const parentWakes = await db.select().from(agentWakeupRequests)
.where(eq(agentWakeupRequests.companyId, identity.companyId))
.then((rows) => rows.filter((row) => row.payload?.issueId === parentIssueId));
expect(parentWakes).toHaveLength(1);
expect(parentWakes[0]).toMatchObject({
reason: "issue_children_completed",
payload: {
issueId: parentIssueId,
completedChildIssueId: childIssueId,
childIssueIds: [childIssueId],
childIssueSummaries: [{
id: childIssueId,
title: "Implement the accepted plan",
status: "done",
summary: "Implemented the accepted plan and passed 44/44 tests.",
}],
childIssueSummaryTruncated: false,
_paperclipWakeContext: {
wakeReason: "issue_children_completed",
const parentWakes = await db.select().from(agentWakeupRequests)
.where(eq(agentWakeupRequests.companyId, identity.companyId))
.then((rows) => rows.filter((row) => row.payload?.issueId === parentIssueId));
const wakeCount = originKind === "task_watchdog" ? 1 : revision;
expect(parentWakes).toHaveLength(wakeCount);
const latestWake = parentWakes.find((wake) => wake.status === "queued");
if (originKind === "task_watchdog" && revision === 2) {
expect(latestWake).toBeUndefined();
} else expect(latestWake).toMatchObject({
reason: "issue_children_completed",
payload: {
issueId: parentIssueId,
completedChildIssueId: childIssueId,
childIssueIds: [childIssueId],
childIssueSummaries: [{
id: childIssueId,
summary: "Implemented the accepted plan and passed 44/44 tests.",
title: "Implement the accepted plan",
status: "done",
summary: `Implemented revision ${revision} and passed 44/44 tests.`,
}],
childIssueSummaryTruncated: false,
_paperclipWakeContext: {
wakeReason: "issue_children_completed",
childIssueSummaries: [{
id: childIssueId,
summary: `Implemented revision ${revision} and passed 44/44 tests.`,
}],
},
},
},
});
});
await finalizeNativeRun({ db, runId, workspaceFinalizeStatus: "succeeded" });
const replayWakes = await db.select().from(agentWakeupRequests)
.where(eq(agentWakeupRequests.companyId, identity.companyId))
.then((rows) => rows.filter((row) => row.payload?.issueId === parentIssueId));
expect(replayWakes).toHaveLength(wakeCount);
expect(new Set(replayWakes.map((wake) => wake.idempotencyKey)).size).toBe(wakeCount);
}
});
it("returns a rejected native completion review to the original agent with the reviewer reason", async () => {
@@ -1999,10 +1999,16 @@ export async function commitNativeStatusDecision(input: {
const parentIsDependent = parent
? dependents.some((dependent) => dependent.id === parent.id)
: false;
// Ordinary children can finish new work after reopening. Key that wake to
// the committed decision; replaying the decision still shares its identity.
// Watchdog children retain their stable key to avoid feedback loops.
const childCompletionRevision = issue.originKind === "task_watchdog"
? ""
: `:${decisionRow.id}`;
for (const dependent of dependents) {
const isCompletedChildParent = parent?.id === dependent.id;
const idempotencyKey = isCompletedChildParent
? `issue_children_completed:${dependent.id}:${input.issueId}`
? `issue_children_completed:${dependent.id}:${input.issueId}${childCompletionRevision}`
: buildIssueBlockersResolvedWakeIdempotencyKey({
dependentIssueId: dependent.id,
resolvedBlockerIssueId: input.issueId,
@@ -2053,7 +2059,7 @@ export async function commitNativeStatusDecision(input: {
issueId: parent.id,
agentId: parent.assigneeAgentId,
reason: "issue_children_completed",
idempotencyKey: `issue_children_completed:${parent.id}:${input.issueId}`,
idempotencyKey: `issue_children_completed:${parent.id}:${input.issueId}${childCompletionRevision}`,
payload: {
completedChildIssueId: input.issueId,
childIssueIds: parent.childIssueIds,