@markjaquith/agency 2.13.0 → 2.15.0

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
@@ -11,7 +11,6 @@ or write.
11
11
  - Git
12
12
  - [GitHub CLI](https://cli.github.com/) for `agency pr create`
13
13
  - OpenCode or Claude Code for `agency work`
14
- - [fzf](https://github.com/junegunn/fzf) for interactive work target selection
15
14
 
16
15
  ## Installation
17
16
 
@@ -146,6 +145,29 @@ The configured command applies only to the writable checkout. Supplemental
146
145
  read-only repositories remain detached Git worktrees at their declared refs so
147
146
  they do not acquire writable branches.
148
147
 
148
+ ### Custom Chooser Command
149
+
150
+ Interactive selection uses a native numbered chooser by default. To use an
151
+ external chooser, configure an argv command in `agency.json`:
152
+
153
+ ```json
154
+ {
155
+ "version": 2,
156
+ "chooserCommand": ["fzf", "--ansi", "--delimiter=\\t", "--with-nth=2.."]
157
+ }
158
+ ```
159
+
160
+ Agency writes one `key<TAB>label` record per choice to the command's stdin. The
161
+ command must write the selected opaque key or selected record to stdout; commands
162
+ such as `["gum", "filter"]` therefore work without wrappers. Exit codes 1 and
163
+ 130, empty stdout, native `q`, and an empty native response cancel selection.
164
+ Other nonzero exits, unknown keys, and invalid native numbers are errors.
165
+
166
+ Selectors are opened only when stdin and stderr are terminals and neither
167
+ `--no-input` nor JSON output is active. Labels use color only when stderr is a
168
+ terminal, `TERM` is not `dumb`, and `NO_COLOR` is unset; otherwise selectors use
169
+ plain labels without ANSI styling or icon-font dependencies.
170
+
149
171
  ## Frontmatter
150
172
 
151
173
  ### Epic
@@ -357,7 +379,7 @@ Inspect tasks:
357
379
  ```text
358
380
  agency task list [--json]
359
381
  agency task show <id> [--json]
360
- agency task status <id> <open|working|delegated|done|dropped> [--json]
382
+ agency task status <id> <open|done|dropped> [--json]
361
383
  ```
362
384
 
363
385
  To add a phase to an existing single-phase task, name the phase that will own
@@ -384,16 +406,48 @@ agency phase create <task-id> <phase-id>
384
406
 
385
407
  agency phase list <task-id> [--json]
386
408
  agency phase show <task-id> <phase-id> [--json]
387
- agency phase status <task-id> <phase-id> <open|working|delegated|done|dropped> [--json]
409
+ agency phase status <task-id> <phase-id> <open|done|dropped> [--json]
388
410
  ```
389
411
 
390
412
  Single-phase tasks and phases store status in YAML. New execution units start
391
413
  `open`, and `agency work` marks the selected execution unit `working` immediately
392
- before launch. Use the status subcommands to mark work `delegated`, `done`,
393
- `dropped`, or open it again. The interactive work selector displays status
394
- markers before execution units. Open, working, and delegated work may transition
395
- to any status. Done and dropped work are terminal and may only remain unchanged
396
- or transition to open; reopen terminal work before changing its outcome.
414
+ before launch. Use claims for coordinated ownership and the status subcommands
415
+ for manual lifecycle overrides. The interactive work selector displays status
416
+ markers before execution units. Existing working and delegated work may be
417
+ released to `open` or assigned a terminal outcome. Done and dropped work are
418
+ terminal and may only remain unchanged or transition to open; reopen terminal
419
+ work before changing its outcome.
420
+
421
+ `delegated` remains readable for existing workbases but cannot be newly assigned.
422
+ Delegation is now explicit: the claimant identifies the orchestrator and the
423
+ runner identifies the assigned agent.
424
+
425
+ ### Claims
426
+
427
+ Claim mutations require the SHA-256 revision exposed by `agency context` or
428
+ `agency graph`. Every operation compares that revision while holding an exclusive
429
+ document lock and atomically replaces the execution document.
430
+
431
+ ```text
432
+ agency claim <task-id> [phase-id] --claimant <id> --runner <id>
433
+ --session-id <id> --revision <sha256> [--expires-at <timestamp>] [--json]
434
+ agency release <task-id> [phase-id] --session-id <id>
435
+ --revision <sha256> [--json]
436
+ agency finish <task-id> [phase-id] --session-id <id>
437
+ --revision <sha256> --outcome <done|dropped> [--json]
438
+ ```
439
+
440
+ An active claim sets status to `working`. Release returns it to `open`; finish
441
+ sets the terminal outcome. Released and finished ownership metadata remains in
442
+ frontmatter. Conflicts return the current revision and complete ownership record
443
+ in the machine error envelope rather than overwriting it. Expired claims may be
444
+ replaced with a revision-guarded claim.
445
+
446
+ `agency work` claims an execution unit before launching its agent. Set
447
+ `AGENCY_CLAIMANT`, `AGENCY_RUNNER`, or `AGENCY_SESSION_ID` to supply orchestrator
448
+ identities; otherwise Agency derives them from the user, selected agent, and
449
+ process. The launched agent receives `AGENCY_SESSION_ID` and
450
+ `AGENCY_CLAIM_REVISION` for a later release or finish operation.
397
451
 
398
452
  ### Archive
399
453
 
package/cli.ts CHANGED
@@ -32,6 +32,13 @@ import { ArchiveService } from "./src/services/ArchiveService"
32
32
  import { IntegrationService } from "./src/services/IntegrationService"
33
33
  import { ContextService } from "./src/services/ContextService"
34
34
  import { GraphService } from "./src/services/GraphService"
35
+ import { ClaimService } from "./src/services/ClaimService"
36
+ import {
37
+ claimCommand,
38
+ claimHelp,
39
+ releaseHelp,
40
+ finishHelp,
41
+ } from "./src/commands/claim"
35
42
  import {
36
43
  collectCommandResult,
37
44
  errorEnvelope,
@@ -53,6 +60,7 @@ const CliLayer = Layer.mergeAll(
53
60
  IntegrationService.Default,
54
61
  ContextService.Default,
55
62
  GraphService.Default,
63
+ ClaimService.Default,
56
64
  )
57
65
 
58
66
  /**
@@ -99,6 +107,61 @@ const VERSION = packageJson.version
99
107
 
100
108
  // Define commands
101
109
  const commands: Record<string, Command> = {
110
+ claim: {
111
+ run: async (args: string[], options: Record<string, any>) => {
112
+ if (options.help) return console.log(claimHelp)
113
+ await runCommand(
114
+ claimCommand({
115
+ operation: "claim",
116
+ taskId: args[0],
117
+ phaseId: args[1],
118
+ claimant: options.claimant,
119
+ runner: options.runner,
120
+ sessionId: options["session-id"],
121
+ revision: options.revision,
122
+ expiresAt: options["expires-at"],
123
+ json: options.json,
124
+ silent: options.silent,
125
+ verbose: options.verbose,
126
+ }),
127
+ )
128
+ },
129
+ },
130
+ release: {
131
+ run: async (args: string[], options: Record<string, any>) => {
132
+ if (options.help) return console.log(releaseHelp)
133
+ await runCommand(
134
+ claimCommand({
135
+ operation: "release",
136
+ taskId: args[0],
137
+ phaseId: args[1],
138
+ sessionId: options["session-id"],
139
+ revision: options.revision,
140
+ json: options.json,
141
+ silent: options.silent,
142
+ verbose: options.verbose,
143
+ }),
144
+ )
145
+ },
146
+ },
147
+ finish: {
148
+ run: async (args: string[], options: Record<string, any>) => {
149
+ if (options.help) return console.log(finishHelp)
150
+ await runCommand(
151
+ claimCommand({
152
+ operation: "finish",
153
+ taskId: args[0],
154
+ phaseId: args[1],
155
+ sessionId: options["session-id"],
156
+ revision: options.revision,
157
+ outcome: options.outcome,
158
+ json: options.json,
159
+ silent: options.silent,
160
+ verbose: options.verbose,
161
+ }),
162
+ )
163
+ },
164
+ },
102
165
  init: {
103
166
  run: async (args: string[], options: Record<string, any>) => {
104
167
  if (options.help) {
@@ -375,6 +438,9 @@ Commands:
375
438
  integration <command> Inspect or sync managed integration files
376
439
  epic <subcommand> Manage epics
377
440
  phase <subcommand> Manage task phases
441
+ claim <task> [phase] Claim an execution unit
442
+ release <task> [phase] Release an execution unit
443
+ finish <task> [phase] Finish an execution unit
378
444
  archive <type> Archive a work item
379
445
  task <subcommand> Manage tasks
380
446
  work [directory|task] Work on an epic, task, or phase
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markjaquith/agency",
3
- "version": "2.13.0",
3
+ "version": "2.15.0",
4
4
  "description": "Manage agentic work across repositories with durable workbases",
5
5
  "keywords": [
6
6
  "agents",
@@ -339,6 +339,7 @@
339
339
  "base": { "type": "string" },
340
340
  "pr": { "type": ["string", "null"] },
341
341
  "status": { "$ref": "#/$defs/status" },
342
+ "claim": { "$ref": "#/$defs/claim" },
342
343
  "sha256": { "type": "string" }
343
344
  }
344
345
  },
@@ -372,6 +373,7 @@
372
373
  "base": { "type": "string" },
373
374
  "pr": { "type": ["string", "null"] },
374
375
  "status": { "$ref": "#/$defs/status" },
376
+ "claim": { "$ref": "#/$defs/claim" },
375
377
  "sha256": { "type": "string" }
376
378
  }
377
379
  },
@@ -393,7 +395,32 @@
393
395
  "branch": { "type": "string" },
394
396
  "base": { "type": "string" },
395
397
  "pr": { "type": ["string", "null"] },
396
- "status": { "$ref": "#/$defs/status" }
398
+ "status": { "$ref": "#/$defs/status" },
399
+ "claim": { "$ref": "#/$defs/claim" }
400
+ }
401
+ },
402
+ "claim": {
403
+ "type": "object",
404
+ "additionalProperties": false,
405
+ "required": [
406
+ "claimant",
407
+ "runner",
408
+ "sessionId",
409
+ "startedAt",
410
+ "targetRevision",
411
+ "state"
412
+ ],
413
+ "properties": {
414
+ "claimant": { "type": "string", "minLength": 1 },
415
+ "runner": { "type": "string", "minLength": 1 },
416
+ "sessionId": { "type": "string", "minLength": 1 },
417
+ "startedAt": { "type": "string", "format": "date-time" },
418
+ "targetRevision": { "type": "string", "pattern": "^[a-f0-9]{64}$" },
419
+ "expiresAt": { "type": "string", "format": "date-time" },
420
+ "state": { "enum": ["active", "released", "finished"] },
421
+ "releasedAt": { "type": "string", "format": "date-time" },
422
+ "finishedAt": { "type": "string", "format": "date-time" },
423
+ "outcome": { "enum": ["done", "dropped"] }
397
424
  }
398
425
  },
399
426
  "repositoryData": {
@@ -213,7 +213,9 @@ details explicitly with `--include bodies|workspace|git|pr`. For large workbases
213
213
  - Epic frontmatter owns the child-task dependency graph.
214
214
  - `pr` is either a GitHub PR URL string or `null`.
215
215
  - Execution-unit `status` is `open`, `working`, `delegated`, `done`, or `dropped`.
216
- New work starts open, and `agency work` marks it working before agent launch.
216
+ New work starts open, and an active claim sets it working before agent launch.
217
+ `delegated` is readable legacy state; distinct claimant and runner identities
218
+ now represent delegation.
217
219
  - Keep directory IDs stable; encode sequencing with `dependsOn`, not numeric
218
220
  directory prefixes.
219
221
  - Do not use YAML duplicate keys, anchors, aliases, or custom tags.
@@ -224,8 +226,17 @@ prose, preserve backlinks and run validation immediately afterward.
224
226
  Update execution status with:
225
227
 
226
228
  ```bash
227
- agency task status <task-id> <open|working|delegated|done|dropped>
228
- agency phase status <task-id> <phase-id> <open|working|delegated|done|dropped>
229
+ agency task status <task-id> <open|done|dropped>
230
+ agency phase status <task-id> <phase-id> <open|done|dropped>
231
+ ```
232
+
233
+ Coordinate execution ownership with a document revision from `agency context`
234
+ or `agency graph`:
235
+
236
+ ```bash
237
+ agency claim <task-id> [phase-id] --claimant <id> --runner <id> --session-id <id> --revision <sha256>
238
+ agency release <task-id> [phase-id] --session-id <id> --revision <sha256>
239
+ agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped>
229
240
  ```
230
241
 
231
242
  ## Archive Completed Work
@@ -141,11 +141,92 @@ describe("strict CLI parsing", () => {
141
141
  [["validate", "one", "two"], "agency validate"],
142
142
  [["context", "one", "two"], "agency context"],
143
143
  [["graph", "extra"], "agency graph"],
144
+ [
145
+ [
146
+ "claim",
147
+ "one",
148
+ "two",
149
+ "three",
150
+ "--claimant",
151
+ "a",
152
+ "--runner",
153
+ "r",
154
+ "--session-id",
155
+ "s",
156
+ "--revision",
157
+ "0".repeat(64),
158
+ ],
159
+ "agency claim",
160
+ ],
161
+ [
162
+ [
163
+ "release",
164
+ "one",
165
+ "two",
166
+ "three",
167
+ "--session-id",
168
+ "s",
169
+ "--revision",
170
+ "0".repeat(64),
171
+ ],
172
+ "agency release",
173
+ ],
174
+ [
175
+ [
176
+ "finish",
177
+ "one",
178
+ "two",
179
+ "three",
180
+ "--session-id",
181
+ "s",
182
+ "--revision",
183
+ "0".repeat(64),
184
+ "--outcome",
185
+ "done",
186
+ ],
187
+ "agency finish",
188
+ ],
144
189
  ] as const) {
145
190
  expectUsageError([...args], usage)
146
191
  }
147
192
  })
148
193
 
194
+ test("validates revision-guarded claim operations", () => {
195
+ const revision = "0".repeat(64)
196
+ expect(
197
+ parseCli([
198
+ "claim",
199
+ "task",
200
+ "phase",
201
+ "--claimant",
202
+ "orchestrator",
203
+ "--runner",
204
+ "agent",
205
+ "--session-id",
206
+ "job-1",
207
+ "--revision",
208
+ revision,
209
+ "--expires-at",
210
+ "2026-07-17T13:00:00.000Z",
211
+ ]),
212
+ ).toMatchObject({ commandName: "claim", args: ["task", "phase"] })
213
+ expect(() =>
214
+ parseCli(["release", "task", "--session-id", "job-1"]),
215
+ ).toThrow("--revision' is required")
216
+ expect(() =>
217
+ parseCli([
218
+ "finish",
219
+ "task",
220
+ "--session-id",
221
+ "job-1",
222
+ "--revision",
223
+ revision,
224
+ "--outcome",
225
+ "working",
226
+ ]),
227
+ ).toThrow("must be 'done' or 'dropped'")
228
+ })
229
+
149
230
  test("accepts context projections and keeps compact command-local", () => {
150
231
  expect(parseCli(["context", ".", "--json", "--compact"])).toMatchObject({
151
232
  commandName: "context",
package/src/cli-parser.ts CHANGED
@@ -59,6 +59,21 @@ const phaseCreateOptions = {
59
59
  "first-phase": { type: "string" },
60
60
  } satisfies OptionConfig
61
61
 
62
+ const claimOptions = {
63
+ ...outputOptions,
64
+ claimant: { type: "string" },
65
+ runner: { type: "string" },
66
+ "session-id": { type: "string" },
67
+ revision: { type: "string" },
68
+ "expires-at": { type: "string" },
69
+ } satisfies OptionConfig
70
+
71
+ const ownedClaimOptions = {
72
+ ...outputOptions,
73
+ "session-id": { type: "string" },
74
+ revision: { type: "string" },
75
+ } satisfies OptionConfig
76
+
62
77
  const commands = {
63
78
  init: {
64
79
  usage: "agency init [path] [--json]",
@@ -258,6 +273,52 @@ const commands = {
258
273
  },
259
274
  },
260
275
  },
276
+ claim: {
277
+ usage: "agency claim <task-id> [phase-id] [options]",
278
+ options: claimOptions,
279
+ command: {
280
+ usage:
281
+ "agency claim <task-id> [phase-id] --claimant <id> --runner <id> --session-id <id> --revision <sha256> [--expires-at <timestamp>] [--json]",
282
+ minArgs: 1,
283
+ maxArgs: 2,
284
+ options: [
285
+ "claimant",
286
+ "runner",
287
+ "session-id",
288
+ "revision",
289
+ "expires-at",
290
+ "json",
291
+ ],
292
+ required: ["claimant", "runner", "session-id", "revision"],
293
+ },
294
+ },
295
+ release: {
296
+ usage: "agency release <task-id> [phase-id] [options]",
297
+ options: ownedClaimOptions,
298
+ command: {
299
+ usage:
300
+ "agency release <task-id> [phase-id] --session-id <id> --revision <sha256> [--json]",
301
+ minArgs: 1,
302
+ maxArgs: 2,
303
+ options: ["session-id", "revision", "json"],
304
+ required: ["session-id", "revision"],
305
+ },
306
+ },
307
+ finish: {
308
+ usage: "agency finish <task-id> [phase-id] [options]",
309
+ options: {
310
+ ...ownedClaimOptions,
311
+ outcome: { type: "string" },
312
+ },
313
+ command: {
314
+ usage:
315
+ "agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped> [--json]",
316
+ minArgs: 1,
317
+ maxArgs: 2,
318
+ options: ["session-id", "revision", "outcome", "json"],
319
+ required: ["session-id", "revision", "outcome"],
320
+ },
321
+ },
261
322
  archive: {
262
323
  usage: "agency archive <epic|task|phase>",
263
324
  options: outputOptions,
@@ -653,6 +714,16 @@ export function parseCli(args: readonly string[]): ParsedCli {
653
714
  if (commandName === "graph") {
654
715
  validateGraphOptions(parsed.values, spec)
655
716
  }
717
+ if (
718
+ commandName === "finish" &&
719
+ parsed.values.outcome !== "done" &&
720
+ parsed.values.outcome !== "dropped"
721
+ ) {
722
+ throw usageError(
723
+ "Option '--outcome' must be 'done' or 'dropped'.",
724
+ spec.usage,
725
+ )
726
+ }
656
727
  if (commandName === "work") {
657
728
  const preparing = commandPositionals[0] === "prepare"
658
729
  if (
package/src/cli.test.ts CHANGED
@@ -81,6 +81,91 @@ describe("CLI", () => {
81
81
  expect(taggedError.stderr).not.toContain("An error has occurred")
82
82
  })
83
83
 
84
+ test("coordinates claims through revision-guarded machine commands", async () => {
85
+ const root = await createTempDir()
86
+ tempDirs.push(root)
87
+ await Bun.write(join(root, "agency.json"), '{"version":2}\n')
88
+ await mkdir(join(root, "repos", "agency"), { recursive: true })
89
+ parseJson(
90
+ await runCli(
91
+ ["task", "create", "claimed", "--repo", "agency", "--json"],
92
+ root,
93
+ ),
94
+ )
95
+ const context = parseJson(
96
+ await runCli(["context", "tasks/claimed", "--json"], root),
97
+ )
98
+ const revision = context.documents.task.sha256
99
+ const claimed = parseJson(
100
+ await runCli(
101
+ [
102
+ "claim",
103
+ "claimed",
104
+ "--claimant",
105
+ "orchestrator",
106
+ "--runner",
107
+ "agent",
108
+ "--session-id",
109
+ "job-1",
110
+ "--revision",
111
+ revision,
112
+ "--json",
113
+ ],
114
+ root,
115
+ ),
116
+ )
117
+ expect(claimed.claim).toMatchObject({
118
+ claimant: "orchestrator",
119
+ runner: "agent",
120
+ sessionId: "job-1",
121
+ state: "active",
122
+ })
123
+
124
+ const conflict = await runCli(
125
+ [
126
+ "claim",
127
+ "claimed",
128
+ "--claimant",
129
+ "other",
130
+ "--runner",
131
+ "other-agent",
132
+ "--session-id",
133
+ "job-2",
134
+ "--revision",
135
+ claimed.revision,
136
+ "--json",
137
+ ],
138
+ root,
139
+ )
140
+ expect(conflict.exitCode).toBe(1)
141
+ expect(JSON.parse(conflict.stdout)).toMatchObject({
142
+ ok: false,
143
+ error: {
144
+ code: "CLAIM_CONFLICT",
145
+ retryable: true,
146
+ fields: { claim: { runner: "agent", sessionId: "job-1" } },
147
+ },
148
+ })
149
+
150
+ const finished = parseJson(
151
+ await runCli(
152
+ [
153
+ "finish",
154
+ "claimed",
155
+ "--session-id",
156
+ "job-1",
157
+ "--revision",
158
+ claimed.revision,
159
+ "--outcome",
160
+ "done",
161
+ "--json",
162
+ ],
163
+ root,
164
+ ),
165
+ )
166
+ expect(finished.claim).toMatchObject({ state: "finished", outcome: "done" })
167
+ })
168
+
84
169
  test("emits one versioned error envelope for usage and command failures", async () => {
85
170
  const usage = await runCli(["unknown", "--json"])
86
171
  expect(usage.exitCode).toBe(1)
@@ -0,0 +1,97 @@
1
+ import { Effect } from "effect"
2
+ import { ClaimService } from "../services/ClaimService"
3
+ import type { BaseCommandOptions } from "../utils/command"
4
+ import { createLoggers } from "../utils/effect"
5
+
6
+ interface ClaimCommandOptions extends BaseCommandOptions {
7
+ readonly operation: "claim" | "release" | "finish"
8
+ readonly taskId?: string
9
+ readonly phaseId?: string
10
+ readonly claimant?: string
11
+ readonly runner?: string
12
+ readonly sessionId?: string
13
+ readonly revision?: string
14
+ readonly expiresAt?: string
15
+ readonly outcome?: string
16
+ readonly json?: boolean
17
+ }
18
+
19
+ export const claimCommand = (options: ClaimCommandOptions) =>
20
+ Effect.gen(function* () {
21
+ const claims = yield* ClaimService
22
+ const { log } = createLoggers(options)
23
+ const cwd = options.cwd ?? process.cwd()
24
+ if (!options.taskId || !options.sessionId || !options.revision) {
25
+ return yield* Effect.fail(new Error("Missing required claim arguments"))
26
+ }
27
+ if (
28
+ options.operation === "claim" &&
29
+ (!options.claimant || !options.runner)
30
+ ) {
31
+ return yield* Effect.fail(
32
+ new Error("Claimant and runner identities are required"),
33
+ )
34
+ }
35
+ if (
36
+ options.operation === "finish" &&
37
+ options.outcome !== "done" &&
38
+ options.outcome !== "dropped"
39
+ ) {
40
+ return yield* Effect.fail(
41
+ new Error("Finish outcome must be done or dropped"),
42
+ )
43
+ }
44
+
45
+ const common = {
46
+ taskId: options.taskId,
47
+ ...(options.phaseId ? { phaseId: options.phaseId } : {}),
48
+ sessionId: options.sessionId,
49
+ revision: options.revision,
50
+ }
51
+ const result =
52
+ options.operation === "claim"
53
+ ? yield* claims.claim(
54
+ {
55
+ ...common,
56
+ claimant: options.claimant!,
57
+ runner: options.runner!,
58
+ ...(options.expiresAt ? { expiresAt: options.expiresAt } : {}),
59
+ },
60
+ cwd,
61
+ )
62
+ : options.operation === "release"
63
+ ? yield* claims.release(common, cwd)
64
+ : yield* claims.finish(
65
+ {
66
+ ...common,
67
+ outcome: options.outcome as "done" | "dropped",
68
+ },
69
+ cwd,
70
+ )
71
+
72
+ const { data: _, ...output } = result
73
+ log(
74
+ options.json
75
+ ? JSON.stringify(output, null, 2)
76
+ : `${options.operation === "claim" ? "Claimed" : options.operation === "release" ? "Released" : "Finished"} ${result.target} at revision ${result.revision}`,
77
+ )
78
+ })
79
+
80
+ export const claimHelp = `
81
+ Usage: agency claim <task-id> [phase-id] --claimant <id> --runner <id> --session-id <id> --revision <sha256>
82
+
83
+ Claim an execution unit. Use distinct claimant and runner identities for delegated
84
+ work. --expires-at accepts an optional future ISO-8601 timestamp.
85
+ `
86
+
87
+ export const releaseHelp = `
88
+ Usage: agency release <task-id> [phase-id] --session-id <id> --revision <sha256>
89
+
90
+ Release an execution unit owned by the session.
91
+ `
92
+
93
+ export const finishHelp = `
94
+ Usage: agency finish <task-id> [phase-id] --session-id <id> --revision <sha256> --outcome <done|dropped>
95
+
96
+ Finish an execution unit owned by the session.
97
+ `
@@ -125,7 +125,7 @@ Subcommands:
125
125
  list <task> List task phases
126
126
  show <task> <phase> Show a phase
127
127
  status <task> <phase> <status>
128
- Set open, working, delegated, done, or dropped
128
+ Set open, done, or dropped
129
129
 
130
130
  Create options:
131
131
  --description <text> Short description of the phase