@allocator-one/harness-cli 1.14.0 → 1.15.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
@@ -1,9 +1,12 @@
1
1
  # @allocator-one/harness-cli
2
2
 
3
3
  The `harness` CLI and MCP server for [Harness](https://harness.infra.one),
4
- allocator.one's issue tracker. It is a standalone Node client that keeps no
5
- local issue state. Issues live on the server, so there is nothing to commit or
6
- sync.
4
+ allocator.one's issue tracker. The same CLI is also published as
5
+ `@allocator-one/operator`, under the command `operator`, for driving
6
+ Operator from the terminal and any agent (see
7
+ [Two names](#two-names-harness-and-operator)). It is a standalone Node client
8
+ that keeps no local issue state. Issues live on the server, so there is
9
+ nothing to commit or sync.
7
10
 
8
11
  ## Install
9
12
 
@@ -18,8 +21,57 @@ Or run it without installing:
18
21
  npx -y @allocator-one/harness-cli ready
19
22
  ```
20
23
 
24
+ The same CLI under its Operator name, as the `operator` command:
25
+
26
+ ```sh
27
+ npm i -g @allocator-one/operator
28
+ operator --help
29
+ ```
30
+
31
+ ```sh
32
+ npx -y @allocator-one/operator --help
33
+ ```
34
+
21
35
  Requires Node >= 20.
22
36
 
37
+ ## Two names: `harness` and `operator`
38
+
39
+ One build ships as two npm packages, always at the same version from the
40
+ same release. Each installs one command:
41
+
42
+ | Package | Command |
43
+ | --- | --- |
44
+ | `@allocator-one/harness-cli` | `harness` |
45
+ | `@allocator-one/operator` | `operator` |
46
+
47
+ Install either package or both. They don't conflict, and `npx -y` or
48
+ `pnpm dlx` runs the package's one command.
49
+
50
+ Both names run the same code against the same server and share everything
51
+ stored locally: the login in `~/.config/harness/credentials.json`, the repo
52
+ config, the caches and the `HARNESS_*` environment variables. Sign in with
53
+ `operator login` or `harness login` and both commands are signed in.
54
+ `logout` under either name signs both out.
55
+
56
+ Only the presentation depends on the name you typed:
57
+
58
+ - `operator --help` lists the Operator commands first (today `jobs`,
59
+ `job`, `watch`, `steer`, `cancel`, `pause`, `continue`, `procedure`, `plan`, `gate`, `conditions`, `assignments`, `tools`, `tool`, `skills` and `mcp`), then the issue-tracker commands under "Issues:", then
60
+ sign-in and housekeeping under "General:". `harness --help` is unchanged,
61
+ with one flat list of commands.
62
+ - `operator mcp` serves the MCP server under the name `operator`, and
63
+ `harness mcp` under `harness`. The tools are the same.
64
+
65
+ Messages, exit codes and `--json` output are the same under both names, so
66
+ a script written for `harness` works with `operator` unchanged. The
67
+ exceptions are the version label and the hints that tell you what to
68
+ type, such as `operator login`: they name the command you typed, because
69
+ someone who installed only `@allocator-one/operator` has no `harness`. The CLI
70
+ tells the names apart by the command path it was started with. Launchers
71
+ that start it by its file path instead, such as npm's Windows shims, pnpm,
72
+ Volta and Yarn PnP, hide that path. In that case the CLI goes by the
73
+ package that installed it, which gives the same answer.
74
+
23
75
  ## Sign in
24
76
 
25
77
  You don't need to configure anything. Commands target
@@ -35,7 +87,32 @@ worktree, and agent slot on the machine.
35
87
  - `harness logout`: revokes the stored login on the server and deletes it
36
88
  locally.
37
89
  - `harness whoami`: shows the resolved host and where the credential came
38
- from.
90
+ from. For a login it also shows the kind of credential, the user, the
91
+ organization and when it expires, as `GET /api/v1/session` describes it.
92
+ On a server without that route, or with an `aone_…` API token, those
93
+ fields are `null` under `--json`. With an outside expert's `pxc_…`
94
+ credential it names the expert's role and plan instead (see
95
+ [Outside experts](#outside-experts)).
96
+
97
+ A login gets one of two kinds of credential. The server picks the kind from
98
+ your standing in the organization you choose:
99
+
100
+ - `harness-cli` is the full credential that Harness staff get.
101
+ - `operator-cli` is the Operator-only credential that any other admin or
102
+ member Operator serves gets. It reaches the Operator commands, `whoami`
103
+ and `logout`, and the server refuses everything else (issues, plans,
104
+ gates) with 403.
105
+
106
+ `login` says after signing in which kind you got (`kind` under `--json`).
107
+ If the new credential fails verification, nothing is stored. A credential
108
+ the browser login just minted is then revoked, in one best-effort attempt,
109
+ so a 30-day credential isn't left behind. A token you pasted is never
110
+ revoked, whatever the failure, since you may be using it elsewhere.
111
+ Holders keep their kind even after gaining staff standing: they log in again
112
+ to get the full credential.
113
+
114
+ `operator login`, `operator logout` and `operator whoami` are the same
115
+ commands and use the same stored login.
39
116
 
40
117
  The automatic login only runs when stdout is a terminal and `CI` is unset
41
118
  or empty. Elsewhere (agents, CI, pipes), a command that has no credential
@@ -43,7 +120,8 @@ prints how to authenticate and exits with code `2`. For those headless
43
120
  contexts, mint an `aone_…` API token (admin → API tokens) with the
44
121
  `harness:read` scope, adding `harness:write` if the workflow changes
45
122
  anything (claim, create, update, comment, close), and export it as
46
- `HARNESS_API_TOKEN`.
123
+ `HARNESS_API_TOKEN`. An outside expert uses their `pxc_…` credential the
124
+ same way (see [Outside experts](#outside-experts)).
47
125
 
48
126
  > **Never set `HARNESS_API_TOKEN` or `HARNESS_ACTOR` in an interactive
49
127
  > session.** The browser login attributes writes to the real user, and
@@ -106,15 +184,32 @@ design, acceptance criteria, notes, and comments (up to 20, cached locally)
106
184
  and prints their paths. Screenshots are often the reproduction, so read
107
185
  them. `--no-images` skips the downloads. If a download fails, a stderr
108
186
  notice says so and the command still succeeds. `harness comments <id>`
109
- prints the full, untruncated comment thread with comment ids. The
187
+ prints the full, untruncated comment thread with comment ids, oldest first.
188
+ It requests up to 200 comments per page and follows the oldest comment id
189
+ until the server reports `has_more=false`. `--json` and the MCP
190
+ `comment_list` tool return the same complete array of full comment objects.
191
+ Missing pagination metadata, repeated comment ids, or an older-page failure
192
+ produce an error without presenting a partial thread as complete. The
110
193
  `Blocked:` line names a cancelled or archived blocker as such
111
194
  (`yes, by CLI-3 (cancelled), CLI-4 (archived), CLI-5`). When the issue has
112
195
  wait conditions, `Waiting:` says how many still hold it, and a
113
196
  `Wait conditions:` section lists each one (`[ ] gate "Finance sign-off" · <id>`,
114
- `[x] not before … (satisfied …)`, `system event`) with the id
197
+ `[ ] gate "Compliance sign-off" [compliance] owned by Compliance officer (role:compliance_officer) · <id>`,
198
+ `[x] not before … (satisfied …)`, `system event`) with the key and id
115
199
  `harness gate satisfy` takes.
116
200
  `harness activity <id>` prints the issue's activity timeline: comments,
117
- creation, and state and assignee changes. It shows the newest 20 entries
201
+ creation, state and assignee changes, wait-condition events
202
+ (`Ada satisfied the gate "Finance sign-off"`), and field changes
203
+ (`changed priority from High to Urgent`, `renamed the issue from “…” to “…”`,
204
+ `edited the description`, `added label regression and removed label idea`,
205
+ `set the parent to CLI-3`, `archived the issue`). A description edit records
206
+ only that it happened, never the text. When a write was made under an agent
207
+ or CLI label, the person comes first and the label follows in quotes:
208
+ `Ada via “Claude Code” moved the issue from Backlog to In progress`. A field
209
+ this CLI does not know yet prints as `changed the <field> field`; `--json`
210
+ passes every entry through as the server sent it. A server that does not
211
+ record field changes yet refuses to serve them; the timeline then comes
212
+ without them, and a stderr notice says so. It shows the newest 20 entries
118
213
  (`--limit` accepts 1–100), and `--all` reads further back.
119
214
 
120
215
  **Create.** Creating an issue before you write code keeps the work tracked:
@@ -263,6 +358,7 @@ harness plan create --file plan.yaml --team IO --idempotency-key q3-report
263
358
  harness plan show <plan-id>
264
359
  harness plan release <plan-id> --collaborator grace@example.com
265
360
  harness gate satisfy IO-121 "Finance sign-off"
361
+ harness gate satisfy IO-121 compliance # by the gate's key
266
362
  ```
267
363
 
268
364
  **The plan file** is the API's request body, as JSON (`.json`) or YAML
@@ -287,6 +383,9 @@ steps:
287
383
  conditions:
288
384
  - kind: gate
289
385
  label: Finance sign-off
386
+ - gate: compliance # a gate with this key; the label defaults to it
387
+ label: Compliance sign-off
388
+ owner: role:compliance_officer
290
389
  - key: send
291
390
  title: Send the report
292
391
  blocked_by: [review]
@@ -306,6 +405,16 @@ steps:
306
405
  `target_id`). The allowed events are `deal_stage_changed`,
307
406
  `opportunity_stage_changed`, `form_submitted`, and `investment_executed`.
308
407
  Every condition must hold before the step is ready.
408
+ - A gate can carry a `key`, a lowercase slug (`a-z`, `0-9` and `-`) unique
409
+ among the step's gates, which `gate satisfy` accepts. `gate: <key>` is
410
+ shorthand for `kind: gate` with that key, labelled with the key unless
411
+ `label` is given. A gate can also have an `owner`: `role:<role>` (one of
412
+ the fund-team roles `gp`, `compliance_officer`, `valuer`, `accountant`,
413
+ `auditor` and `counsel`), `user:<id or email>` (a member who can use
414
+ Operator), or `expert:<email>` (an outside expert, never a member's
415
+ email). Only the owner can tick an owned gate. You can't name yourself as
416
+ an owner, and that includes a role you hold. `owner: null` or no owner
417
+ leaves the gate unowned.
309
418
  - A plan has at most 100 steps, its root included.
310
419
  - `--team` (or `HARNESS_TEAM` or the repo config) is the root's team and the
311
420
  default team for people's steps. `--idempotency-key` works as it does for
@@ -322,13 +431,39 @@ harness: The plan is not valid; nothing was changed (HTTP 422)
322
431
  plan: A plan with Operator steps needs a positive spend budget [spend_budget_required]
323
432
  ```
324
433
 
434
+ A gate's owner is checked the same way. The owner violations are
435
+ `invalid_owner`, `unknown_role`, `unknown_owner`, `owner_is_member`,
436
+ `owner_is_actor` (you named yourself), `owner_not_allowed` (an owner on a
437
+ condition that is not a gate), `expert_owner_requires_plan`, and
438
+ `duplicate_gate_key`. A `gate:` that conflicts with the condition's `kind`
439
+ or `key` is `invalid_condition`.
440
+
325
441
  **Show.** `harness plan show <plan-id>` prints the plan's status, budgets,
326
442
  revision, and spend estimate, then every step in tree order (indented by
327
443
  depth) with its key, executor, state, and status. The status is `ready`,
328
444
  `held` (edited since the release), `not in revision` (added after it),
329
445
  `waiting` (on a wait condition), `container`, `done`, `cancelled`,
330
446
  `archived`, or `-`. The edges and each step's wait conditions come after
331
- the steps.
447
+ the steps. Each gate shows its key and owner. A `Waiting for:` section then
448
+ lists the open gates each step waits for, with the gate's owner and the
449
+ people who can tick it. When nobody can, it says `nobody can tick`:
450
+
451
+ ```text
452
+ Waiting for:
453
+ IO-121 "Compliance sign-off" [compliance] · Compliance officer (role:compliance_officer) · can tick: Ada Lovelace, Grace Hopper (expert, Acme LLP)
454
+ IO-122 "Legal review" [legal] · Bob Smith (user:7d3c2a1e-5b4f-4c8d-9e0a-1f2b3c4d5e6f) · nobody can tick
455
+ IO-123 "Smoke test" · no owner, anyone with standing can tick
456
+ ```
457
+
458
+ An owner shows as its name followed by how the server names it: a role
459
+ (`Compliance officer (role:compliance_officer)`), a member by user id
460
+ (`Bob Smith (user:<uuid>)`, even when the plan document named them by
461
+ email), or an outside expert by email (`val@example.com (expert)`).
462
+
463
+ `--json` prints the plan as the server sends it, including each step's
464
+ `waiting_for` (`condition_id`, `key`, `label`, `owner`, and `holders`:
465
+ `[]` when nobody can tick, `null` for an unowned gate or when the holders
466
+ are not known).
332
467
 
333
468
  **Edit.** `harness plan edit <plan-id> --file edits.yaml` changes step
334
469
  executors and keys, naming each step by its `step` key or `issue`
@@ -351,19 +486,389 @@ gate.
351
486
  you: it freezes the next revision and lets ready steps run.
352
487
  `--collaborator <id-or-email>` (repeatable) adds people to the plan room
353
488
  where its Operator steps run, and `--retry-budget` and
354
- `--spend-budget-cents` set the budgets. `harness plan hold <plan-id>`
355
- pauses every step until the plan is released again.
489
+ `--spend-budget-cents` set the budgets. Each Operator step runs as a job
490
+ (`harness jobs` names its plan step), and the changes a job proposes need a
491
+ person's approval in the app, at the job's approval link. The CLI never
492
+ approves. `harness plan hold <plan-id>` pauses every step until the plan is
493
+ released again.
356
494
 
357
495
  **Gates.** `harness gate satisfy <issue> <gate>` ticks a gate and
358
496
  `harness gate unsatisfy <issue> <gate>` unticks it. `<gate>` is the gate's
359
- label or its condition id from `harness show`. A label matches exactly
360
- first, then ignoring case, and a label that more than one gate carries is
361
- refused with their ids. Ticking a gate requires staff standing in the
362
- organization.
497
+ key, its label, or its condition id, all shown by `harness show`. None of
498
+ the three outranks the others: when `<gate>` exactly matches more than one
499
+ condition, say one gate's key and another gate's label, the command
500
+ refuses and lists each one's id, key, and label instead of choosing. Pass
501
+ the condition id of the one you mean. Only when nothing matches exactly
502
+ does a label or key match ignoring case, and that too is refused when more
503
+ than one gate matches. Ticking a gate requires staff standing in the
504
+ organization or a place in its plan's room. The server refuses an
505
+ Operator-only login (`operator-cli`) here, even for a person in the plan's
506
+ room: it reaches only the Operator commands, `whoami` and `logout`. Only
507
+ the owner can tick an owned gate, and only its owner or Harness staff can
508
+ untick it. Anyone else gets an error that names the gate's owner as of
509
+ the refusal, which the server sends with it, and exits `1`:
510
+
511
+ ```text
512
+ harness: Cannot satisfy gate "Compliance sign-off" on IO-121: only its owner, Compliance officer (role:compliance_officer), can tick it (HTTP 403)
513
+ ```
514
+
515
+ A gate with no owner by then gets the same error without a name. Against
516
+ a server that does not send the owner, the command reads the conditions
517
+ again to find it.
518
+
519
+ **Conditions.** `harness conditions <issue>` lists an issue's wait
520
+ conditions, oldest first, one per line as `show` lists them: each one's
521
+ box, what it waits for (a gate's label, key and owner), when and by whom it
522
+ was satisfied, and its id. `--json` is the list as the server sends it.
363
523
 
364
524
  A server without the plan API answers the plan commands with a 404, and the
365
525
  error says that the server may predate plans.
366
526
 
527
+ ## Outside experts
528
+
529
+ An outside expert invited to a plan can hold a `pxc_…` credential. The
530
+ expert mints it in their own expert view, with a fresh code from their
531
+ authenticator app, and it lasts at most 30 days. It reaches only:
532
+
533
+ - their assignments (`GET /api/v1/expert/assignments`);
534
+ - on a step assigned to them, the gates they own: listed, ticked and
535
+ unticked.
536
+
537
+ To the CLI it is an ordinary bearer credential. In a terminal, paste it
538
+ into `harness login --no-browser`, which verifies it against the
539
+ assignments and stores it like any login. For an agent or a script, export
540
+ it as `HARNESS_API_TOKEN`. Writes are attributed to the expert's own
541
+ account either way.
542
+
543
+ ```sh
544
+ harness login --no-browser # paste the pxc_ credential
545
+ harness whoami # the expert's role and plan
546
+ harness assignments # their steps, and the gates they own on each
547
+ harness conditions IO-130 # their gates on one step
548
+ harness gate satisfy IO-130 valuation # tick one, by key, label or id
549
+ ```
550
+
551
+ `harness whoami` says the credential is an outside expert's and names the
552
+ expert's role, the plan and how many steps are assigned to them:
553
+
554
+ ```text
555
+ Host: https://harness.infra.one (via stored login)
556
+ Credential: stored login, an outside expert's pxc_ credential
557
+ Expert: Valuer on plan 0b6f5e9a-6c1e-4d3f-9a39-2f1f8f7e4c11 (released) · 1 assigned step
558
+ Reaches: its own assignments and the gates it owns on them (assignments, conditions, gate)
559
+ Verified: ok (server API 1.4.693)
560
+ ```
561
+
562
+ `whoami --json` always carries `expert`: `null` for any other credential,
563
+ and for a `pxc_…` one `{id, role, plan: {id, status} | null,
564
+ assigned_steps}`, beside the usual `url`, `source`, `verified`,
565
+ `server_version` and `host_source`.
566
+
567
+ `harness assignments` lists each step assigned to the expert that they
568
+ still hold, in identifier order: its state, due date, how many open issues
569
+ block it, the gates on it they own, and its description (left out for a
570
+ step Operator runs). The expert view never names a member: a gate shows its
571
+ owner as a role, or `you` for the expert's own gate, and who ticked it as
572
+ `you`, the role it was ticked as, `an outside expert` or `a member`.
573
+ `--json` is the server's data unchanged: `expert` (`{id, role}`), `plan`
574
+ (`{id, status}` or `null`) and `assignments`, each with `identifier`,
575
+ `title`, `description`, `state` (`{name, type}`), `due_date`,
576
+ `open_blockers` and `gates`. Any other credential gets a 403
577
+ (`expert_credential_required`).
578
+
579
+ `harness conditions <issue>` and `harness gate satisfy|unsatisfy` read and
580
+ tick only the expert's own gates. After a tick the CLI counts only the
581
+ expert's own gates still open, because the step's other conditions are out
582
+ of the expert's view. A step that isn't assigned to the expert answers 404,
583
+ which the error says.
584
+
585
+ **Every other command that reads or changes Harness or Operator data is
586
+ refused** with a 403 `expert_bound_credential`, reads and writes alike.
587
+ (`whoami`, `version`, `login` and `logout` keep working: they use only the
588
+ routes the credential reaches.) The CLI says what the credential reaches
589
+ and exits `1`:
590
+
591
+ ```text
592
+ harness: This expert credential may only read its own assignments and tick its own gates — an outside expert's pxc_ credential only lists its own assignments and reads and ticks the gates it owns on them: assignments, conditions <issue> and gate satisfy|unsatisfy (assignment_list, condition_list and gate_set over MCP) (HTTP 403)
593
+ ```
594
+
595
+ The platform reads the credential, the expert's seat and the account again
596
+ on every request. An expired or revoked credential, a revoked or ended
597
+ seat, a suspended account or a changed authenticator answers 401 at once.
598
+ With a `pxc_…` credential the error lists those causes, and the command
599
+ exits `1`. If the seat is still open, mint a new credential in the expert
600
+ view. `harness logout` forgets a stored `pxc_…` credential without
601
+ revoking it, because the session route refuses it; revoke it in the expert
602
+ view to end it at once. Storing a `pxc_…` credential replaces (and
603
+ revokes) a stored member login, so log in again to use the other
604
+ commands.
605
+
606
+ ## Operator jobs
607
+
608
+ `jobs`, `job` and `watch` read the Operator jobs you see in the Chat Mode
609
+ workbench, in the organization your credential belongs to. They need the
610
+ `operator:read` scope (a browser login carries it) and an admin or member
611
+ role there.
612
+
613
+ ```sh
614
+ harness jobs --status waiting --mine
615
+ harness job <job-id>
616
+ harness watch <job-id>
617
+ ```
618
+
619
+ Everything is by reference: states, codes, counts, ids and workbench links.
620
+ None of them shows a job's objective text, because the API never returns it:
621
+ it can quote what Operator read. Each job shows its `objective_kind` and the
622
+ workbench link where its objective can be read instead. Approval happens in the
623
+ app. No command approves anything; they print the approval link.
624
+
625
+ - `harness jobs` lists jobs newest first, with their state, what they wait
626
+ on, kind, owner and plan step, followed by the approval link of each job
627
+ waiting for approval. `--status` takes comma-separated job states (`draft`,
628
+ `planning`, `ready`, `running`, `waiting_for_approval`,
629
+ `waiting_for_human`, `paused`, `failed`, `needs_reconciliation`,
630
+ `completed`, `cancelled`) or the aliases `open` (not done), `waiting` and
631
+ `done`. `--mine` lists only your own jobs. Pages hold 20 jobs (`--limit`,
632
+ at most 50). A page can come back short, or even empty, while older jobs
633
+ exist, because the server drops the jobs you may not see after reading
634
+ the page. stderr says so. `--all` follows every page, 50 jobs each unless
635
+ `--limit` says otherwise, for up to 100 pages; past that it stops, exits
636
+ `1` and asks you to narrow the list with `--status` or `--mine`.
637
+ - `harness job <job-id>` shows the job's status, plan version, progress,
638
+ remaining window and budget, spend, the change set waiting for approval
639
+ with its link, the grant, verification, effect counts, blockers,
640
+ artifacts, pinned skill, people, saved procedures, and what you can do
641
+ in the workbench.
642
+ - `harness job <job-id> --changes` shows the job's latest change set
643
+ instead, by reference: its id, version, status, digest and plan version,
644
+ and each action's type, version, order, effect count, required
645
+ capabilities and digest, with the approval link. Values and targets never
646
+ leave the app; review them there. A job that has proposed nothing prints
647
+ "No pending change set" (`change_set` is `null` under `--json`).
648
+ - `harness job <job-id> --people` shows who is on the job, by name: the
649
+ requester, the execution principal (the person it runs as), the owner,
650
+ the approvers and the collaborators. A person the job doesn't name shows
651
+ as their user id; the API never sends emails. Pass both flags to see
652
+ both, and `--json` returns them under the job's id, state, link and
653
+ approval link.
654
+ - `harness watch <job-id>` prints the job's events and state changes as
655
+ they happen, polling every 2 seconds, until the job completes, fails or
656
+ is cancelled, or a person has to act. A person has to act exactly when
657
+ the job's `waiting_on` is set: an approval, a person, reconciliation,
658
+ verification or delivery. `watch` stops there with the link to act on:
659
+ the approval link when the job waits for approval. A job paused with
660
+ nothing for a person to do, such as one whose deliveries are still
661
+ sending, resumes by itself, so `watch` keeps following it, every 10
662
+ seconds. `--through-waits` follows through a person's waits too, also
663
+ every 10 seconds. `--after <seq>` starts after an event sequence instead
664
+ of replaying the history. `--json` prints one document when the watch
665
+ ends, with the outcome, the job's last state, the events read (ids, codes,
666
+ numbers and booleans only), and `next_after` to resume from.
667
+ - `watch` only reads, so it retries a failed poll three times (after 1, 2
668
+ and 4 seconds, or longer when the server sends `Retry-After`) when the API
669
+ is unreachable, answers with a 5xx, or rate-limits. If the poll still
670
+ fails, or you press Ctrl-C, stderr names the `--after` to resume from, and
671
+ `--json` still prints the document with the events read so far. When the
672
+ reader of its output goes away (`watch <id> | head -1`), it stops polling
673
+ and exits quietly.
674
+
675
+ `watch` exits `0` when the job completed, `4` when a person has to act,
676
+ `5` when the job failed or was cancelled, and `130` on Ctrl-C (see
677
+ [Scripting contract](#scripting-contract)).
678
+
679
+ A 403 names why the server refused: Operator switched off for the
680
+ organization (`operator_disabled`), no admin or member role there
681
+ (`operator_not_permitted`), a credential that acts as no person
682
+ (`actor_required`), or a missing `operator:read` scope (`forbidden`). A job
683
+ that does not exist, is in another organization, or is not shared with you
684
+ answers the same 404.
685
+
686
+ ## Steering, cancelling and pausing a job
687
+
688
+ `steer`, `cancel` and `pause` change or stop an Operator job the way the
689
+ workbench's controls do, under the same rules and with your current
690
+ authority. They need the `operator:write` scope (a browser login carries it)
691
+ and an admin or member role in the organization.
692
+
693
+ ```sh
694
+ harness steer <job-id> "Only use the audited Q3 figures"
695
+ harness steer <job-id> --instruction-file instruction.md # "-" reads stdin
696
+ harness cancel <job-id>
697
+ harness pause <job-id>
698
+ ```
699
+
700
+ - `harness steer <job-id> <instruction>` posts the instruction in the job's
701
+ room, attributed to you, wherever the app offers Steer. The job's owner
702
+ and collaborators may steer. Work the instruction changes needs a fresh
703
+ approval: earlier approvals stop applying to it. Steering a running job
704
+ stops its current run so it can take the instruction. An instruction is 1
705
+ to 10,000 characters once trimmed, and is sent trimmed; a longer or blank
706
+ one is refused before anything is sent (exit `2`). Quote it: unquoted
707
+ words that look like options (`--json`, `--help`) are read as options, not
708
+ text. Put `--` before an instruction that starts with `-`, or pass it with
709
+ `--instruction-file <path>` (`-` for stdin), which takes any text as it
710
+ is. Words and `--instruction-file` together are a usage error.
711
+ - `harness cancel <job-id>` ends the job. Effects already committed stay
712
+ committed, and the command prints their counts; staged effects that never
713
+ ran count as unknown.
714
+ - `harness pause <job-id>` holds a running job for a person without
715
+ resetting its active-time budget. Continue it to run again.
716
+
717
+ Only the job's execution principal may cancel or pause it. Each command
718
+ prints the job as `job` shows it afterwards (`--json`: the same document),
719
+ and approving still happens in the app.
720
+
721
+ None of them is idempotent, so the CLI sends each one once and never retries
722
+ it, not even after a timeout or a server error. What a failure means depends
723
+ on whether the request can have reached the server:
724
+
725
+ - It cannot have (the name did not resolve, the connection was refused, the
726
+ TLS handshake failed): nothing changed, and the command exits `3` like
727
+ any unreachable API.
728
+ - It may have (a timeout, a dropped connection, a 5xx, an answer that cannot
729
+ be read): the outcome is unknown. The command exits `1` and says so; check
730
+ `harness job <job-id>` before trying again.
731
+
732
+ The server's message comes first, followed by what to do about it:
733
+
734
+ | Status | Code | Meaning |
735
+ | --- | --- | --- |
736
+ | 400 | `bad_request` | The request carried a field the server does not accept. Nothing changed; update the CLI. |
737
+ | 403 | `principal_required` | `cancel` and `pause` are the job's execution principal's own. Its owner or a collaborator can still steer it, which stops its current run. |
738
+ | 403 | `operator_disabled`, `operator_not_permitted`, `actor_required`, `forbidden`, `run_bound_credential` | As for `jobs`, except that `forbidden` means a missing `operator:write` scope. |
739
+ | 404 | `not_found` | The job does not exist, is in another organization, or is not shared with you. |
740
+ | 409 | `invalid_state` | The app wouldn't offer the control now: Steer on a finished, failed or awaiting-approval job (or one needing reconciliation), Cancel on a job that already ended, Pause on a job that isn't running. |
741
+ | 409 | `reconciliation_required` | Steer: partial effects need reconciling in the app first. |
742
+ | 409 | `deliveries_unsettled` | Steer: a delivery is still sending, or its outcome is unknown. If `job` shows the job waiting on delivery, settle it in the app (an unknown delivery never settles by itself); otherwise steer again once it has sent. |
743
+ | 409 | `principal_inactive` | The job's execution principal no longer holds the rights to run it. Nothing changed. |
744
+ | 409 | `cancellation_scope_changed` | Cancel: the job's work changed while it was being cancelled. It was not cancelled; read it and decide again. |
745
+ | 422 | `invalid_instruction`, `validation_error` | Steer: the instruction is blank or too long, or the job's room refused it. |
746
+
747
+ ## Continue and saved procedures
748
+
749
+ ```sh
750
+ harness continue <job-id> # a new window for a job, as Continue does in the app
751
+ harness procedure save <job-id> --name "Monthly close" # save a verified job as a procedure
752
+ harness procedure show <procedure-id> # its versions and the parameters a run takes
753
+ harness procedure run <procedure-id> --param period=2026-09 --param count:=3
754
+ harness procedure run <procedure-id> --params-file params.json --version 2
755
+ ```
756
+
757
+ They act with the authority, budgets and kill controls of the Chat Mode
758
+ workbench, and need the `operator:write` scope (a browser login carries it;
759
+ `procedure show` needs `operator:read`).
760
+
761
+ - `continue` opens a new audited window for a job that ran out of time or
762
+ waits on a person, or retries a failed run or verification, exactly as
763
+ Continue does in the app. Only the job's execution principal, the person
764
+ it runs as, may continue it. It prints the job afterwards, as `job` does.
765
+ - `procedure save` saves a job's recipe as a versioned procedure. Only the
766
+ job's owner can save it, and only once it completed with its work
767
+ verified. Saving again saves another procedure.
768
+ - `procedure show` prints the procedure's name, the job it was saved from
769
+ and that job's link, the current version, each version's recipe digest,
770
+ capability ids and action roster, and the JSON schema of the parameters a
771
+ run takes. It never shows a version's recipe or anything the saved job
772
+ read. When the schema's `scoped_inputs` require `contact_reconciliation`,
773
+ a run needs `--group <contact-group-id>` and `--source <uploaded-file-id>`.
774
+ - `procedure run` starts a new job from the procedure, as Rerun does in the
775
+ app. Nothing of the saved job carries over: the run has fresh inputs, your
776
+ own current rights, and a change set that needs its own approval, which
777
+ happens in the app. It prints the new job's link. `--version <n>` runs only
778
+ while that version is still current. `--param name=value` passes a string
779
+ and `--param name:=<json>` a JSON value (repeatable); `--params-file <path>`
780
+ (`-` for stdin) passes one JSON object instead. A bad `--param` (one
781
+ without `=`, a name given twice or with whitespace around it), an
782
+ unreadable or invalid `--params-file`, both together, a `--version` that
783
+ isn't a positive integer, `--version`, `--params-file`, `--group` or
784
+ `--source` given twice, or a `--group` or `--source` with whitespace around
785
+ it exits `2` before anything is sent.
786
+ - When the new procedure or job can't be read back as you, the answer is
787
+ only its id and link, for people and under `--json` alike.
788
+
789
+ **None of these writes is idempotent**, so none is ever retried
790
+ automatically, not after a timeout or a 5xx either: a second continue opens
791
+ another window, a second save saves another procedure, a second run starts
792
+ another job. Their exit codes say whether trying again is safe:
793
+
794
+ - `3` only when the request never reached the server (the name did not
795
+ resolve, the connection was refused, the TLS handshake failed). Nothing
796
+ changed.
797
+ - `1` with "Outcome unknown" when it may have: a timeout, a dropped
798
+ connection, an answer that could not be read (then the server received
799
+ it), or a 5xx. Check what the error names first (`job <id>`, the job's
800
+ procedures, or `jobs` for a run's new job) before trying again. A 503
801
+ `service_unavailable` from `procedure run` is the exception: the server
802
+ refused the run before starting it.
803
+
804
+ The MCP tools follow the same rule: only a request that never reached the
805
+ server says "cannot reach the API".
806
+
807
+ A `continue` that answers 404 may still have happened, if the job went out
808
+ of your sight just as it was continued: check `job <id>` first.
809
+ `job <id> --changes` reads the job and then its change set; when a new
810
+ change set lands between the two reads, stderr says so.
811
+
812
+ Every refusal exits `1` with the server's message, what it means and the
813
+ HTTP status:
814
+
815
+ | Status | Code | Meaning |
816
+ | --- | --- | --- |
817
+ | 400 | `bad_request` | The request carried a field the server does not accept. Nothing changed; update the CLI. |
818
+ | 403 | `principal_required` | `continue`: only the job's execution principal may continue it. |
819
+ | 403 | `owner_required` | `procedure save`: only the job's owner can save it. |
820
+ | 403 | `operator_disabled`, `operator_not_permitted`, `actor_required`, `forbidden` | As for every Operator command; `forbidden` here means a missing `operator:write` scope. |
821
+ | 404 | `not_found` | The same answer for a job or procedure that doesn't exist, is in another organization, or that you can't see. |
822
+ | 409 | `invalid_state`, `reconciliation_required`, `principal_inactive`, `deliveries_unsettled` | `continue`: nothing to continue, partial effects to reconcile in the app, a principal who lost the rights, or a delivery still sending or of unknown outcome (settle it in the app when `job <id>` shows it waiting on delivery). |
823
+ | 409 | `version_conflict`, `concurrent_change` | `procedure run`: the named version is no longer current, or the procedure changed while the run started. No job was started. |
824
+ | 422 | `not_saveable`, `validation_error` | `procedure save`: the job isn't completed and verified, or the name holds control characters. |
825
+ | 422 | `invalid_rerun` | `procedure run`: the inputs don't fit the parameters, or your current rights don't cover the run. No job was started. |
826
+ | 503 | `service_unavailable` | `procedure run`: Operator's operational profile is unavailable. No job was started. |
827
+
828
+ ## Operator's catalog
829
+
830
+ What Operator can do in your organization, and the skills it has:
831
+
832
+ ```sh
833
+ harness tools # the capabilities Operator can use now, by id
834
+ harness tools --domain crm # one domain's
835
+ harness tools domains # every domain, with how many
836
+ harness tools search "find a party" # by keyword, best match first
837
+ harness tool crm.party.search # one capability, with its whole input schema
838
+ harness tool crm.party.search --schema result # a read's result schema
839
+ harness skills # the skills assigned to Operator
840
+ ```
841
+
842
+ - **Only what Operator can use now.** A capability that ships disabled or is
843
+ switched off for the organization never appears, so there is no
844
+ "enabled" column.
845
+ - **Kinds.** Each capability is a `read`, a `write`, or a `send` (a write
846
+ delivered beyond the platform, such as an email). Write and send
847
+ capabilities, and any proposal, always need a person's approval in the app
848
+ before they take effect. A capability without a kind shows as `unknown`,
849
+ never as a read. These commands run and approve nothing.
850
+ - **Pages.** The index comes 25 to a page and a search 15, with no page size
851
+ to choose. When there is more, stderr says how many there are in all and
852
+ names the next page's cursor: `--all` follows every page (up to 200), and
853
+ `--cursor <cursor>` reads the next one, or every page from there with
854
+ `--all`. `tools search` takes `--domain`, `--all`, and `--cursor` as well.
855
+ An empty `--domain` or `--cursor` is a usage error.
856
+ - **Schemas.** `tool <id>` fetches every page of the schema and prints it
857
+ whole. A page that is out of order, skipped, of another schema, or at odds
858
+ with the others' fragment count fails the command, and no schema is
859
+ printed. `--json` returns the capability with its `input_schema` (or
860
+ `result_schema`) as an object. Only a read has a result schema.
861
+ - **Skills** show each skill's invocation name, title, the SHA-256 of its
862
+ content, and its id. Their content and description are never exposed.
863
+
864
+ Operator needs the `operator:read` scope, an admin or member role in the
865
+ organization the credential is bound to, and Operator switched on there.
866
+ Login tokens carry the scope; `aone_` tokens need to be minted with it. A
867
+ refusal exits 1 with the server's message and what it means
868
+ (`operator_disabled`, `operator_not_permitted`, `actor_required`). `tool`
869
+ answers a capability that doesn't exist, ships disabled, or is switched off
870
+ with the same 404.
871
+
367
872
  ## Uploads
368
873
 
369
874
  `harness upload <path>` uploads a file and prints a markdown reference to
@@ -465,7 +970,10 @@ can read.
465
970
  | `0` | Success, including `ready --claim` finding nothing to claim. |
466
971
  | `1` | The command failed. Causes include an API error (the message gives the HTTP status, followed by any per-field errors or plan violations, one per line), a response the CLI cannot interpret, a partial failure across several ids, or a local refusal such as an unreadable or oversized upload. |
467
972
  | `2` | Usage or configuration error, including "not signed in" outside a terminal. Nothing was sent. |
468
- | `3` | The API was unreachable (DNS, TLS, connection refused, or timeout). Retry with backoff, and if the API stays down, say so instead of dropping tracked work. |
973
+ | `3` | The API was unreachable (DNS, TLS, connection refused, or timeout). Retry with backoff, and if the API stays down, say so instead of dropping tracked work. Operator's writes (`steer`, `cancel`, `pause`, `continue`, `procedure save`, `procedure run`) exit `3` only when the request never reached the server; when it may have, they exit `1` with "Outcome unknown", and you check what the error names (`job`, or `jobs` for a run's new job) before running one again. |
974
+ | `4` | `watch` only: a person has to act on the Operator job (its `waiting_on` is set: an approval, a person, reconciliation, verification or delivery). The link to act on is on stdout. |
975
+ | `5` | `watch` only: the Operator job failed or was cancelled. |
976
+ | `130` | `watch` only: interrupted with Ctrl-C. stderr names the `--after` to resume from. |
469
977
 
470
978
  ## MCP server
471
979
 
@@ -475,14 +983,23 @@ can read.
475
983
  claude mcp add --scope user harness -- npx -y @allocator-one/harness-cli mcp
476
984
  ```
477
985
 
478
- With a global install, `harness mcp` works as the command instead of the
479
- `npx` form. Any other MCP client can use the same command:
986
+ Or register it under its Operator name. The server then reports its name
987
+ as `operator`, and it serves the same tools:
988
+
989
+ ```sh
990
+ claude mcp add --scope user operator -- npx -y @allocator-one/operator mcp
991
+ ```
992
+
993
+ Register one of the two, not both, or every tool appears twice.
994
+
995
+ With a global install, `harness mcp` (or `operator mcp`) works as the
996
+ command instead of the `npx` form. Any other MCP client can use the same command:
480
997
 
481
998
  ```json
482
999
  { "mcpServers": { "harness": { "command": "npx", "args": ["-y", "@allocator-one/harness-cli", "mcp"] } } }
483
1000
  ```
484
1001
 
485
- The server exposes 58 tools:
1002
+ The server exposes 72 tools:
486
1003
 
487
1004
  | Area | Tools |
488
1005
  | --- | --- |
@@ -494,7 +1011,11 @@ The server exposes 58 tools:
494
1011
  | Labels | `label_list`, `label_create`, `label_update`, `label_delete` |
495
1012
  | Teams and states | `team_list`, `team_create`, `team_rename`, `team_set_default`, `workflow_states`, `state_add`, `state_update`, `state_order`, `state_delete` |
496
1013
  | People | `member_list`, `member_add`, `member_set_role`, `user_list` |
497
- | Plans and gates | `plan_create`, `plan_show`, `plan_edit`, `plan_release`, `plan_hold`, `gate_set` |
1014
+ | Plans and gates | `plan_create`, `plan_show`, `plan_edit`, `plan_release`, `plan_hold`, `gate_set`, `condition_list` |
1015
+ | Outside experts | `assignment_list` |
1016
+ | Operator jobs | `jobs_list`, `job_show`, `job_changes`, `job_start`, `job_steer`, `job_cancel`, `job_pause` |
1017
+ | Continue and procedures | `job_continue`, `procedure_save`, `procedure_show`, `procedure_run` |
1018
+ | Operator's catalog | `tools_search` (without `query` it browses the index; `domain`, `cursor`, `all`), `skills_list` |
498
1019
  | Other | `stats`, `upload` |
499
1020
 
500
1021
  The tools cover the issue workflow and the admin surface. Some commands are
@@ -513,16 +1034,50 @@ Some tools behave differently from their commands:
513
1034
  - `state_delete` and `label_delete` don't need the CLI's `--yes`.
514
1035
  - `plan_create` takes the plan as an object (`plan`), with `team` and
515
1036
  `idempotency_key` beside it, instead of a file. Its schema describes the
516
- steps and conditions, and keys inside `plan` that the schema does not
517
- know are still sent, so the server reports them. `plan_edit` takes
1037
+ steps and conditions, including a gate's `key` and `owner` and the
1038
+ `gate: <key>` shorthand, which makes `kind` optional. Keys inside `plan`
1039
+ that the schema does not know are still sent, so the server reports them. `plan_edit` takes
518
1040
  `steps`, `retry_budget`, and `spend_budget_cents` directly. As with every
519
1041
  tool, a top-level argument the tool does not declare is dropped.
520
- `gate_set` takes `issue`, `gate`, and `satisfied`.
1042
+ `gate_set` takes `issue`, `gate` (a key, label, or condition id), and
1043
+ `satisfied`.
521
1044
  - An API error's details come back in the tool error text: a validation
522
1045
  error's fields, and an invalid plan's violations, one per line by step
523
1046
  key.
1047
+ - With an outside expert's `pxc_…` credential, `assignment_list`,
1048
+ `condition_list` and `gate_set` work on the expert's own steps and gates.
1049
+ Every other tool returns the `expert_bound_credential` refusal, with the
1050
+ same explanation the CLI prints.
524
1051
  - `issue_list` with `ready: true` gives every issue an `agent_step` flag,
525
1052
  read from its team as `ready` does.
1053
+ - `jobs_list` takes `status` as a list of job states and aliases. There is
1054
+ no watch tool: poll `job_show` until `state` is `completed`, `failed` or
1055
+ `cancelled`, or `waiting_on` is set. A person has to act exactly when
1056
+ `waiting_on` is set; a job paused without it resumes by itself. Neither
1057
+ tool approves anything; give a person the `approval_url`.
1058
+ - `job_start` is the CLI's `run`: `outcome`, and optionally `budget` (US
1059
+ dollars; only the organization's admins) and `skill`; there is no `team`.
1060
+ It returns `job_id`, `state`, `workbench_url` and, when a change set
1061
+ waits, `approval_url`, and never approves: give a person the link. Each
1062
+ call starts another job, so it is never retried; after an "Outcome
1063
+ unknown" error look with `jobs_list` (`mine`) before calling again. It
1064
+ carries `idempotentHint: false` and `destructiveHint: false`. The endpoint
1065
+ needs a server that has the Operator start-job API.
1066
+ - `job_steer`, `job_cancel` and `job_pause` return the job as `job_show`
1067
+ does. None is idempotent, so none is retried: after an error that leaves
1068
+ the outcome unknown, read the job with `job_show` before calling one
1069
+ again. All three carry `idempotentHint: false`. `job_cancel` sets
1070
+ `destructiveHint: true`; `job_steer` (it voids earlier approvals and stops
1071
+ a run) and `job_pause` (it stops a run) leave MCP's default, which is also
1072
+ destructive. Approving stays in the app.
1073
+ - `job_changes` is the CLI's `job --changes`. There is no tool for
1074
+ `job --people`: `job_show` already returns the requester, execution
1075
+ principal, participants and approvers.
1076
+ - `procedure_run` takes `parameters` as an object, beside `version`,
1077
+ `group_id` and `source_resource_id`. `job_continue`, `procedure_save` and
1078
+ `procedure_run` are never retried and carry `idempotentHint: false`; a
1079
+ failed call's error says what to check before calling again, and only a
1080
+ request that never reached the server says "cannot reach the API".
526
1081
 
527
1082
  For example, `issue_list` with `{"ready": true, "team": "CLI"}` returns the
528
1083
  ready queue, and `issue_claim` with `{"id": "CLI-12"}` claims an issue
@@ -575,7 +1130,7 @@ automatic login asks for confirmation first, as described above. Plain
575
1130
  | Variable | Effect |
576
1131
  | --- | --- |
577
1132
  | `HARNESS_API_URL` | Target host (https, or http against localhost only). |
578
- | `HARNESS_API_TOKEN` | An `aone_…` API token. It takes precedence over the stored login. Headless use only. |
1133
+ | `HARNESS_API_TOKEN` | An `aone_…` API token, or an outside expert's `pxc_…` credential. It takes precedence over the stored login. Headless use only. |
579
1134
  | `HARNESS_ACTOR` | Write attribution, sent only when set. Without it the server attributes writes to the signed-in user, or for `aone_` tokens to the token's name. Headless use only. |
580
1135
  | `HARNESS_TEAM` | Default team key for commands that take `--team`. |
581
1136
  | `HARNESS_CACHE_DIR` | Where `show` stores downloaded images. Must be an absolute path. |
@@ -591,7 +1146,7 @@ automatic login asks for confirmation first, as described above. Plain
591
1146
  | `~/.config/harness/credentials.json` | The stored login: one `{url, token}` pair, mode 0600. |
592
1147
  | `~/.config/harness/trusted-hosts.json` | Repo-config hosts you agreed to log in to. |
593
1148
  | `~/.cache/harness/attachments/` | Images downloaded by `show` (or `HARNESS_CACHE_DIR`). |
594
- | `~/.cache/harness/update-check.json` | The daily update-check stamp. |
1149
+ | `~/.cache/harness/update-check.json` | The daily update-check stamp (`update-check-operator.json` for `@allocator-one/operator`). |
595
1150
  | `~/.cache/harness/upgrade.lock` | Held while an automatic upgrade runs. |
596
1151
 
597
1152
  None of these files is tracker state: issues stay on the server. The
@@ -603,6 +1158,9 @@ longer need them.
603
1158
  **Notice.** Each run reads the cached stamp and prints at most one stderr
604
1159
  line when a newer release exists:
605
1160
  `harness: 1.13.0 is available (you have 1.12.1) — npm i -g @allocator-one/harness-cli@latest`.
1161
+ The notice follows the package you installed. An `@allocator-one/operator`
1162
+ install checks that package's own latest release, keeps its own stamp, and
1163
+ is told to upgrade `@allocator-one/operator`.
606
1164
  When the stamp is more than a day old, a detached background process
607
1165
  refreshes it from the npm registry. A command never waits on that network
608
1166
  request. The notice applies only to global and project-local installs, not
@@ -611,7 +1169,8 @@ to `npx` or a git checkout.
611
1169
  **Automatic upgrade on HTTP 426.** The CLI sends its version with every
612
1170
  request, and the server refuses versions below its minimum with a 426. In
613
1171
  that case the CLI runs
614
- `npm install --global --ignore-scripts @allocator-one/harness-cli@latest`,
1172
+ `npm install --global --ignore-scripts @allocator-one/harness-cli@latest`
1173
+ (or `@allocator-one/operator@latest`, for an install of that package),
615
1174
  re-runs the original command, and exits with that command's exit code.
616
1175
  Re-running is safe only for this status. The server answers 426 before
617
1176
  authentication and before the request handler, so the refused request