@markjaquith/agency 2.40.0 → 2.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -569,7 +569,7 @@ Targeted commands accept `--epic`, `--task`, and `--phase` where those entity
569
569
  kinds apply. A phase selector requires a task selector. Entity selectors cannot
570
570
  be mixed with positional target IDs, and an epic selector cannot be mixed with
571
571
  task or phase selectors. This makes commands such as
572
- `agency phase status done --task ship --phase release --workbase primary --no-input`
572
+ `agency phase status working --task ship --phase release --workbase primary --no-input`
573
573
  fully independent of process cwd and prompts.
574
574
 
575
575
  Inspect tasks:
@@ -577,7 +577,7 @@ Inspect tasks:
577
577
  ```text
578
578
  agency task list [filters] [--json]
579
579
  agency task show <id> [--json]
580
- agency task status <id> <open|done|dropped> [--json]
580
+ agency task status <id> <open|working|dropped> [--json]
581
581
  agency task update <id> [metadata options] [--json]
582
582
  agency task rename <id> <new-id> [--json]
583
583
  agency task move <id> (--epic <epic-id> | --no-epic) [--json]
@@ -616,7 +616,7 @@ agency phase create <task-id> <phase-id>
616
616
 
617
617
  agency phase list <task-id> [filters] [--json]
618
618
  agency phase show <task-id> <phase-id> [--json]
619
- agency phase status <task-id> <phase-id> <open|done|dropped> [--json]
619
+ agency phase status <task-id> <phase-id> <open|working|dropped> [--json]
620
620
  agency phase update <task-id> <phase-id> [metadata options] [--json]
621
621
  agency phase rename <task-id> <phase-id> <new-id> [--json]
622
622
  agency phase dependency <add|remove> <task-id> <phase-id> <dependency-id>
@@ -639,6 +639,8 @@ writing anything.
639
639
  Single-phase tasks and phases store status in YAML. New execution units start
640
640
  `open`, and `agency work` marks the selected execution unit `working` immediately
641
641
  before launch. Running `agency work` again can relaunch unclaimed `working` work.
642
+ The `done` status is reserved for an authoritative merged pull request and is
643
+ applied by `agency sync --apply`, not by task or phase status commands.
642
644
  Use explicit claims only when an external orchestrator needs coordinated
643
645
  ownership. The interactive work selector displays status markers before
644
646
  execution units. Existing working and delegated work may be released to `open`
@@ -672,11 +674,12 @@ agency finish <task-id> [phase-id] --session-id <id>
672
674
  --revision <sha256> --outcome <done|dropped> [--json]
673
675
  ```
674
676
 
675
- An active claim sets status to `working`. Release returns it to `open`; finish
676
- sets the terminal outcome. Released and finished ownership metadata remains in
677
- frontmatter. Conflicts return the current revision and complete ownership record
678
- in the machine error envelope rather than overwriting it. Expired claims may be
679
- replaced with a revision-guarded claim.
677
+ An active claim sets status to `working`. Release returns it to `open`. Finish
678
+ records the claim outcome and ownership history; a `done` claim outcome leaves
679
+ the execution unit `working` until its pull request is merged, while `dropped`
680
+ remains terminal. Conflicts return the current revision and complete ownership
681
+ record in the machine error envelope rather than overwriting it. Expired claims
682
+ may be replaced with a revision-guarded claim.
680
683
 
681
684
  `agency work` does not claim execution units. It refuses active explicit claims,
682
685
  marks open execution work `working`, and launches the runner. External
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.40.0",
3
+ "version": "2.40.1",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -118,11 +118,12 @@ independently meaningful tasks need coordination.
118
118
 
119
119
  1. Re-run `agency validate` and repository checks.
120
120
  2. Create a PR only when requested: `agency pr create <task> [phase]`.
121
- 3. Record terminal state only when the requested outcome is true. A created PR
122
- alone does not make work `done` if completion requires merge.
123
- 4. If the session has a claim, use revision-guarded `agency finish`; otherwise
124
- use the task or phase status command. Use `dropped` only for intentionally
125
- abandoned work.
121
+ 3. Keep committed work `working` while its pull request is open. After the
122
+ authoritative pull request merges, use `agency sync --apply` to reconcile it
123
+ to `done`.
124
+ 4. If the session has a claim, use revision-guarded `agency finish`; a successful
125
+ claim outcome closes ownership without marking unmerged work `done`. Use
126
+ `dropped` only for intentionally abandoned work.
126
127
  5. Report the durable status and PR URL. Do not manually remove the worktree.
127
128
 
128
129
  ## Human Launch vs Active Agent
@@ -89,7 +89,7 @@ agency task create <id> --multi-phase [--ticket-url <url>]
89
89
  [--description <text>] [--epic <id>] [--json]
90
90
  agency task list [filters] [--json]
91
91
  agency task show <id> [--json]
92
- agency task status <id> <open|done|dropped> [--json]
92
+ agency task status <id> <open|working|dropped> [--json]
93
93
  agency task update <id> [metadata options] [--if-revision <hash>] [--json]
94
94
  agency task rename <id> <new-id> [--if-revision <hash>] [--json]
95
95
  agency task move <id> (--epic <epic-id> | --no-epic)
@@ -102,7 +102,7 @@ agency phase create <task-id> <phase-id> --repo <alias> --branch <name>
102
102
  [--depends-on <phase-id>...] [--first-phase <phase-id>] [--json]
103
103
  agency phase list <task-id> [filters] [--json]
104
104
  agency phase show <task-id> <phase-id> [--json]
105
- agency phase status <task-id> <phase-id> <open|done|dropped> [--json]
105
+ agency phase status <task-id> <phase-id> <open|working|dropped> [--json]
106
106
  agency phase update <task-id> <phase-id> [metadata options]
107
107
  [--if-revision <hash>] [--json]
108
108
  agency phase rename <task-id> <phase-id> <new-id>
@@ -137,6 +137,8 @@ readable legacy state but cannot be newly assigned.
137
137
  - An execution unit has one writable `repo`; plural `repos` are read-only
138
138
  `{ repo, ref }` entries and cannot repeat the writable alias.
139
139
  - A writable `(repo, branch)` pair belongs to exactly one active execution unit.
140
+ - `done` represents an authoritative merged pull request and is reconciled by
141
+ `agency sync --apply`; committed or review-ready work remains `working`.
140
142
  - Only `done` satisfies dependencies. `dropped` is terminal but blocks dependents.
141
143
  - Epic task dependencies live in `EPIC.md`; phase dependencies live in `TASK.md`.
142
144
  - IDs remain stable; use `dependsOn`, not numeric directory prefixes, for order.
@@ -184,15 +184,17 @@ agency release checkout ui --session-id session-123 \
184
184
 
185
185
  ## Finish Verified Work
186
186
 
187
- Only after the assigned completion condition is true:
187
+ After the claimed run completes successfully:
188
188
 
189
189
  ```bash
190
190
  agency finish checkout ui --session-id session-123 \
191
191
  --revision <current-sha256> --outcome done --json
192
192
  ```
193
193
 
194
- Do not substitute `phase status done` for `finish` when an active claim exists;
195
- `finish` preserves ownership history and revision safety.
194
+ Do not substitute a phase status mutation for `finish`; `finish` preserves
195
+ ownership history and revision safety. A `done` claim outcome leaves unmerged
196
+ execution work `working`. After the authoritative pull request merges, use
197
+ `agency sync --apply` to reconcile the execution unit to `done`.
196
198
 
197
199
  ## Create A Pull Request
198
200
 
package/src/cli.test.ts CHANGED
@@ -244,6 +244,10 @@ describe("CLI", () => {
244
244
  ),
245
245
  )
246
246
  expect(finished.claim).toMatchObject({ state: "finished", outcome: "done" })
247
+ expect(
248
+ parseJson(await runCli(["task", "show", "claimed", "--json"], root)).data
249
+ .status,
250
+ ).toBe("working")
247
251
  })
248
252
 
249
253
  test("routes graph mutations through structured output", async () => {
@@ -476,7 +480,7 @@ describe("CLI", () => {
476
480
  )
477
481
  }
478
482
  parseJson(
479
- await runCli(["task", "status", "finished", "done", "--json"], root),
483
+ await runCli(["task", "status", "finished", "dropped", "--json"], root),
480
484
  )
481
485
 
482
486
  const human = await runCli(["next"], root)
@@ -490,9 +494,9 @@ describe("CLI", () => {
490
494
  expect(result.excluded).toMatchObject([
491
495
  {
492
496
  key: "task/finished",
493
- status: "done",
497
+ status: "dropped",
494
498
  terminal: true,
495
- blockers: [{ kind: "status", reason: "Task status is done" }],
499
+ blockers: [{ kind: "status", reason: "Task status is dropped" }],
496
500
  },
497
501
  ])
498
502
 
@@ -504,8 +508,8 @@ describe("CLI", () => {
504
508
  error: {
505
509
  code: "EXECUTION_BLOCKED",
506
510
  fields: {
507
- status: "done",
508
- blockers: [{ kind: "status", reason: "Task status is done" }],
511
+ status: "dropped",
512
+ blockers: [{ kind: "status", reason: "Task status is dropped" }],
509
513
  },
510
514
  },
511
515
  })
@@ -1131,18 +1135,18 @@ status: open
1131
1135
  }
1132
1136
 
1133
1137
  parseJson(
1134
- await runCli(["task", "status", "example", "done", "--json"], root),
1138
+ await runCli(["task", "status", "example", "dropped", "--json"], root),
1135
1139
  )
1136
1140
  const blocked = await runCli(
1137
1141
  ["work", "--task", "example", "--runner", "noop"],
1138
1142
  root,
1139
1143
  )
1140
1144
  expect(blocked.exitCode).toBe(1)
1141
- expect(blocked.stderr).toContain("Task status is done")
1145
+ expect(blocked.stderr).toContain("Task status is dropped")
1142
1146
  expect(
1143
1147
  parseJson(await runCli(["task", "show", "example", "--json"], root))
1144
1148
  .data.status,
1145
- ).toBe("done")
1149
+ ).toBe("dropped")
1146
1150
 
1147
1151
  const resumedTask = await runCli(
1148
1152
  ["work", "--task", "example", "--runner", "noop", "--force"],
@@ -1150,7 +1154,7 @@ status: open
1150
1154
  )
1151
1155
  expect(resumedTask).toMatchObject({ exitCode: 0, stderr: "" })
1152
1156
  expect(resumedTask.stdout).toContain(
1153
- "Reopened task/example from done as working",
1157
+ "Reopened task/example from dropped as working",
1154
1158
  )
1155
1159
  expect(
1156
1160
  parseJson(await runCli(["task", "show", "example", "--json"], root))
@@ -93,5 +93,6 @@ Release an execution unit owned by the session.
93
93
  export const finishHelp = `
94
94
  Usage: agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped>
95
95
 
96
- Finish an execution unit owned by the session.
96
+ Finish a claim owned by the session. A done claim outcome leaves unmerged work
97
+ working; agency sync --apply marks the execution unit done after merge.
97
98
  `
@@ -275,7 +275,7 @@ Subcommands:
275
275
  list <task> List task phases
276
276
  show <task> <phase> Show a phase
277
277
  status <task> <phase> <status>
278
- Set open, working, done, or dropped
278
+ Set open, working, or dropped
279
279
  update <task> <phase> Update phase metadata
280
280
  rename <task> <phase> <new-id>
281
281
  Rename a phase and update dependencies
@@ -74,7 +74,7 @@ describe("status command", () => {
74
74
  await runTestEffect(
75
75
  task({
76
76
  subcommand: "status",
77
- args: ["finished", "done"],
77
+ args: ["finished", "dropped"],
78
78
  cwd: root,
79
79
  silent: true,
80
80
  }),
@@ -239,13 +239,13 @@ describe("task and phase command JSON output", () => {
239
239
  runTestEffect(
240
240
  phase({
241
241
  subcommand: "status",
242
- args: ["multi", "first", "done"],
242
+ args: ["multi", "first", "working"],
243
243
  cwd: root,
244
244
  json: true,
245
245
  }),
246
246
  ),
247
247
  )
248
- expect(JSON.parse(phaseLogs[0]!).data.status).toBe("done")
248
+ expect(JSON.parse(phaseLogs[0]!).data.status).toBe("working")
249
249
 
250
250
  await runTestEffect(
251
251
  task({
@@ -263,13 +263,13 @@ describe("task and phase command JSON output", () => {
263
263
  runTestEffect(
264
264
  task({
265
265
  subcommand: "status",
266
- args: ["single-status", "done"],
266
+ args: ["single-status", "working"],
267
267
  cwd: root,
268
268
  json: true,
269
269
  }),
270
270
  ),
271
271
  )
272
- expect(JSON.parse(taskLogs[0]!).data.status).toBe("done")
272
+ expect(JSON.parse(taskLogs[0]!).data.status).toBe("working")
273
273
  })
274
274
 
275
275
  test("converts a single-phase task with an explicit first phase ID", async () => {
@@ -423,7 +423,7 @@ Subcommands:
423
423
  create <id> Create a task without prompting
424
424
  list List tasks
425
425
  show <id> Show a task
426
- status <id> <status> Set open, working, done, or dropped
426
+ status <id> <status> Set open, working, or dropped
427
427
  update <id> Update task metadata
428
428
  rename <id> <new-id> Rename a task and update graph references
429
429
  move <id> Move a task with --epic or --no-epic
package/src/readiness.ts CHANGED
@@ -5,9 +5,9 @@ export interface ReadinessBlocker {
5
5
  }
6
6
 
7
7
  export const WORK_STATUS_TRANSITIONS = {
8
- open: ["open", "working", "delegated", "done", "dropped"],
9
- working: ["open", "working", "delegated", "done", "dropped"],
10
- delegated: ["open", "working", "delegated", "done", "dropped"],
8
+ open: ["open", "working", "delegated", "dropped"],
9
+ working: ["open", "working", "delegated", "dropped"],
10
+ delegated: ["open", "working", "delegated", "dropped"],
11
11
  done: ["open", "done"],
12
12
  dropped: ["open", "dropped"],
13
13
  } as const satisfies Record<WorkStatus, readonly WorkStatus[]>
@@ -139,7 +139,7 @@ describe("claim service", () => {
139
139
  ),
140
140
  ),
141
141
  )
142
- expect(finished.data.status).toBe("done")
142
+ expect(finished.data.status).toBe("working")
143
143
  expect(finished.claim).toMatchObject({
144
144
  state: "finished",
145
145
  finishedAt: "2026-07-17T12:45:00.000Z",
@@ -538,7 +538,11 @@ export class ClaimService extends Effect.Service<ClaimService>()(
538
538
  outcome: input.outcome,
539
539
  }
540
540
  return {
541
- data: { ...data, status: input.outcome, claim },
541
+ data: {
542
+ ...data,
543
+ status: input.outcome === "done" ? "working" : "dropped",
544
+ claim,
545
+ },
542
546
  claim,
543
547
  }
544
548
  },
@@ -80,14 +80,15 @@ describe("IntegrationService", () => {
80
80
  expect(body).toContain("External orchestrators claim before launching")
81
81
  expect(body).toContain("Run `agency validate`")
82
82
  expect(body).toContain("only with explicit user intent")
83
- expect(body).toContain("An execution unit is `working`")
83
+ expect(body).toContain("An execution unit remains `working`")
84
84
  expect(body).toContain("It becomes `done`")
85
- expect(body).toContain("solely because its PR")
85
+ expect(body).toContain("Do not mark committed")
86
86
  expect(body).toContain("creating or updating a PR")
87
87
  expect(body).toContain("marking it ready")
88
88
  expect(body).toMatch(/completing\s+a refinement loop/)
89
89
  expect(body).toContain("pausing or handing off")
90
- expect(body).toContain("`agency task status` or `agency phase status`")
90
+ expect(body).toContain("`agency finish`")
91
+ expect(body).toContain("`agency sync --apply`")
91
92
  expect(body).toContain("`TASK.md` or `PHASE.md`")
92
93
  expect(body).toContain("PR state, current head, diff summary")
93
94
  expect(body).toContain("Run `agency validate` before reporting completion")
@@ -487,6 +487,12 @@ export class PhaseService extends Effect.Service<PhaseService>()(
487
487
  })
488
488
  }
489
489
  if (!canTransitionStatus(record.data.status, validStatus)) {
490
+ if (validStatus === "done") {
491
+ return yield* new PhaseError({
492
+ message:
493
+ "Work becomes done after its authoritative pull request is merged; run 'agency sync --apply'",
494
+ })
495
+ }
490
496
  return yield* new PhaseError({
491
497
  message: `Cannot transition phase '${id}' from ${record.data.status} to ${validStatus}; reopen it first`,
492
498
  })
@@ -263,11 +263,13 @@ process.exit(${exitCode})
263
263
  await createTask()
264
264
  await runTestEffect(
265
265
  TaskService.pipe(
266
- Effect.flatMap((service) => service.setStatus("example", "done", root)),
266
+ Effect.flatMap((service) =>
267
+ service.setStatus("example", "dropped", root),
268
+ ),
267
269
  ),
268
270
  )
269
271
 
270
- await expect(createPullRequest()).rejects.toThrow("Task status is done")
272
+ await expect(createPullRequest()).rejects.toThrow("Task status is dropped")
271
273
  expect(
272
274
  await Bun.file(join(root, "tasks/example/code/agency")).exists(),
273
275
  ).toBe(false)
@@ -390,6 +390,99 @@ process.stdout.write(${JSON.stringify(JSON.stringify(record))})
390
390
  expect("pr" in task.data && task.data.pr).toEqual(record)
391
391
  })
392
392
 
393
+ test("marks a successfully finished claim done only after merge", async () => {
394
+ await runTestEffect(
395
+ TaskService.pipe(
396
+ Effect.flatMap((service) =>
397
+ service.create(
398
+ {
399
+ id: "finished-claim",
400
+ ticketUrl: null,
401
+ repo: "agency",
402
+ branch: "feat/example",
403
+ base: "main",
404
+ },
405
+ root,
406
+ ),
407
+ ),
408
+ ),
409
+ )
410
+ await runTestEffect(
411
+ WorktreeService.pipe(
412
+ Effect.flatMap((service) =>
413
+ service.materialize("finished-claim", undefined, root),
414
+ ),
415
+ ),
416
+ )
417
+ await git(
418
+ ["remote", "set-url", "origin", "git@github.com:example/agency.git"],
419
+ join(root, "repos/agency"),
420
+ )
421
+ const initial = await runTestEffect(
422
+ ClaimService.pipe(
423
+ Effect.flatMap((service) =>
424
+ service.inspect("finished-claim", undefined, root),
425
+ ),
426
+ ),
427
+ )
428
+ const claimed = await runTestEffect(
429
+ ClaimService.pipe(
430
+ Effect.flatMap((service) =>
431
+ service.claim(
432
+ {
433
+ taskId: "finished-claim",
434
+ claimant: "orchestrator",
435
+ runner: "agent",
436
+ sessionId: "session-1",
437
+ revision: initial.revision,
438
+ },
439
+ root,
440
+ ),
441
+ ),
442
+ ),
443
+ )
444
+ const finished = await runTestEffect(
445
+ ClaimService.pipe(
446
+ Effect.flatMap((service) =>
447
+ service.finish(
448
+ {
449
+ taskId: "finished-claim",
450
+ sessionId: "session-1",
451
+ revision: claimed.revision,
452
+ outcome: "done",
453
+ },
454
+ root,
455
+ ),
456
+ ),
457
+ ),
458
+ )
459
+ expect(finished.data).toMatchObject({
460
+ status: "working",
461
+ claim: { state: "finished", outcome: "done" },
462
+ })
463
+
464
+ const synced = await runTestEffect(
465
+ SyncService.pipe(
466
+ Effect.flatMap((service) =>
467
+ service.reconcile({ cwd: root, apply: true }),
468
+ ),
469
+ ),
470
+ )
471
+ expect(synced.changes.map((change) => change.kind)).toContain("mark-done")
472
+ expect(
473
+ await runTestEffect(
474
+ TaskService.pipe(
475
+ Effect.flatMap((service) => service.show("finished-claim", root)),
476
+ ),
477
+ ),
478
+ ).toMatchObject({
479
+ data: {
480
+ status: "done",
481
+ claim: { state: "finished", outcome: "done" },
482
+ },
483
+ })
484
+ })
485
+
393
486
  test("materializes missing workspaces but leaves branch conflicts unresolved", async () => {
394
487
  for (const [id, branch] of [
395
488
  ["missing", "feat/missing"],
@@ -318,22 +318,23 @@ describe("task and phase services", () => {
318
318
  ),
319
319
  ),
320
320
  ).rejects.toThrow("Delegation requires explicit ownership")
321
- await runTestEffect(
322
- TaskService.pipe(
323
- Effect.flatMap((service) =>
324
- service.setStatus("single-status", "done", root),
325
- ),
326
- ),
327
- )
328
321
  await expect(
329
322
  runTestEffect(
330
323
  TaskService.pipe(
331
324
  Effect.flatMap((service) =>
332
- service.setStatus("single-status", "dropped", root),
325
+ service.setStatus("single-status", "done", root),
333
326
  ),
334
327
  ),
335
328
  ),
336
- ).rejects.toThrow("reopen it first")
329
+ ).rejects.toThrow("authoritative pull request is merged")
330
+ const droppedTask = await runTestEffect(
331
+ TaskService.pipe(
332
+ Effect.flatMap((service) =>
333
+ service.setStatus("single-status", "dropped", root),
334
+ ),
335
+ ),
336
+ )
337
+ expect(droppedTask.data.status).toBe("dropped")
337
338
 
338
339
  await runTestEffect(
339
340
  TaskService.pipe(
@@ -389,7 +390,7 @@ describe("task and phase services", () => {
389
390
  ),
390
391
  ),
391
392
  ),
392
- ).rejects.toThrow("reopen it first")
393
+ ).rejects.toThrow("authoritative pull request is merged")
393
394
  await runTestEffect(
394
395
  PhaseService.pipe(
395
396
  Effect.flatMap((service) =>
@@ -397,17 +398,15 @@ describe("task and phase services", () => {
397
398
  ),
398
399
  ),
399
400
  )
400
- expect(
401
- (
402
- await runTestEffect(
403
- PhaseService.pipe(
404
- Effect.flatMap((service) =>
405
- service.setStatus("multi-status", "implementation", "done", root),
406
- ),
401
+ await expect(
402
+ runTestEffect(
403
+ PhaseService.pipe(
404
+ Effect.flatMap((service) =>
405
+ service.setStatus("multi-status", "implementation", "done", root),
407
406
  ),
408
- )
409
- ).data.status,
410
- ).toBe("done")
407
+ ),
408
+ ),
409
+ ).rejects.toThrow("authoritative pull request is merged")
411
410
  await expect(
412
411
  runTestEffect(
413
412
  TaskService.pipe(
@@ -270,6 +270,12 @@ export class TaskService extends Effect.Service<TaskService>()("TaskService", {
270
270
  })
271
271
  }
272
272
  if (!canTransitionStatus(record.data.status, validStatus)) {
273
+ if (validStatus === "done") {
274
+ return yield* new TaskError({
275
+ message:
276
+ "Work becomes done after its authoritative pull request is merged; run 'agency sync --apply'",
277
+ })
278
+ }
273
279
  return yield* new TaskError({
274
280
  message: `Cannot transition task '${id}' from ${record.data.status} to ${validStatus}; reopen it first`,
275
281
  })
@@ -48,17 +48,19 @@ reason to edit `agency.json` or `repos/` by hand.
48
48
 
49
49
  ## Closeout
50
50
 
51
- An execution unit is `working` while implementation or requested delivery work
52
- remains. It becomes `done` when both are complete, even if its PR remains open
53
- for review or merge. Do not leave a task or phase `working` solely because its PR
54
- is open; if merge was requested, merge remains delivery work.
51
+ An execution unit remains `working` after implementation is committed and while
52
+ its pull request is open. It becomes `done` only after its authoritative pull
53
+ request is merged and Agency reconciles that state. Do not mark committed or
54
+ review-ready work `done` manually.
55
55
 
56
56
  At each closeout trigger (creating or updating a PR, marking it ready, completing
57
57
  a refinement loop, or pausing or handing off completed implementation work):
58
58
 
59
- - Finish an active claim with the current revision via `agency finish`.
60
- Otherwise use `agency task status` or `agency phase status` to set the
61
- execution unit's current status.
59
+ - Finish an active claim with the current revision via `agency finish`; a
60
+ successful claim outcome leaves unmerged work `working`. For unclaimed work,
61
+ keep the execution unit `working` through review and merge.
62
+ - After merge, run `agency sync --apply` to reconcile the execution unit to
63
+ `done`.
62
64
  - Refresh durable delivery context in `TASK.md` or `PHASE.md`, including recorded
63
65
  PR state, current head, diff summary, and verification results after later
64
66
  pushes when those details are maintained there.