@allocator-one/harness-cli 1.13.0 → 1.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 +726 -31
- package/dist/chunk-I2S2JVCQ.js +4761 -0
- package/dist/cli.js +495 -173
- package/dist/{server-CYBKYG5E.js → server-O7CX2ZCC.js} +333 -15
- package/package.json +3 -1
- package/spec/openapi.json +13784 -1496
- package/dist/chunk-XFMECLOU.js +0 -2258
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.
|
|
5
|
-
|
|
6
|
-
|
|
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,54 @@ 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 CLI
|
|
67
|
+
tells the names apart by the command path it was started with. Launchers
|
|
68
|
+
that start it by its file path instead, such as npm's Windows shims, pnpm,
|
|
69
|
+
Volta and Yarn PnP, hide that path. In that case the CLI goes by the
|
|
70
|
+
package that installed it, which gives the same answer.
|
|
71
|
+
|
|
23
72
|
## Sign in
|
|
24
73
|
|
|
25
74
|
You don't need to configure anything. Commands target
|
|
@@ -35,7 +84,11 @@ worktree, and agent slot on the machine.
|
|
|
35
84
|
- `harness logout`: revokes the stored login on the server and deletes it
|
|
36
85
|
locally.
|
|
37
86
|
- `harness whoami`: shows the resolved host and where the credential came
|
|
38
|
-
from.
|
|
87
|
+
from. With an outside expert's `pxc_…` credential it also names the
|
|
88
|
+
expert's role and plan (see [Outside experts](#outside-experts)).
|
|
89
|
+
|
|
90
|
+
`operator login`, `operator logout` and `operator whoami` are the same
|
|
91
|
+
commands and use the same stored login.
|
|
39
92
|
|
|
40
93
|
The automatic login only runs when stdout is a terminal and `CI` is unset
|
|
41
94
|
or empty. Elsewhere (agents, CI, pipes), a command that has no credential
|
|
@@ -43,7 +96,8 @@ prints how to authenticate and exits with code `2`. For those headless
|
|
|
43
96
|
contexts, mint an `aone_…` API token (admin → API tokens) with the
|
|
44
97
|
`harness:read` scope, adding `harness:write` if the workflow changes
|
|
45
98
|
anything (claim, create, update, comment, close), and export it as
|
|
46
|
-
`HARNESS_API_TOKEN`.
|
|
99
|
+
`HARNESS_API_TOKEN`. An outside expert uses their `pxc_…` credential the
|
|
100
|
+
same way (see [Outside experts](#outside-experts)).
|
|
47
101
|
|
|
48
102
|
> **Never set `HARNESS_API_TOKEN` or `HARNESS_ACTOR` in an interactive
|
|
49
103
|
> session.** The browser login attributes writes to the real user, and
|
|
@@ -58,20 +112,47 @@ numbering, `ALL-` numbers up to the import ceiling came from Linear, and
|
|
|
58
112
|
## Daily workflow
|
|
59
113
|
|
|
60
114
|
```sh
|
|
61
|
-
harness ready --claim
|
|
115
|
+
harness ready --claim # claim the team's next ready, unassigned issue
|
|
62
116
|
harness show CLI-12 # read it, including attached screenshots
|
|
63
117
|
# … do the work …
|
|
64
118
|
harness close CLI-12 --reason "Implemented in 1a2b3c4"
|
|
65
119
|
```
|
|
66
120
|
|
|
67
121
|
**Queue.** `harness ready` lists open, unblocked issues, highest priority
|
|
68
|
-
first. Blocked issues, `hold`-labeled issues, and parents with
|
|
69
|
-
are
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
`--team`.
|
|
122
|
+
first. Blocked issues, `hold`-labeled issues, and parents with sub-issues
|
|
123
|
+
that are not completed are excluded. Only a completed blocker resolves: a
|
|
124
|
+
cancelled or archived one still blocks until it is completed or the
|
|
125
|
+
dependency is removed or re-pointed. An issue that is waiting on a wait
|
|
126
|
+
condition (see [Plans](#plans)) is not ready either. `harness blocked` lists
|
|
127
|
+
issues that are waiting on a blocker.
|
|
128
|
+
Both commands take `--team`. `ready` marks agent steps, the plan steps
|
|
129
|
+
Operator runs, with `[agent]` before the title and a legend under the table,
|
|
130
|
+
and `--json` gives every issue an `agent_step` flag. Only the plan driver
|
|
131
|
+
claims an agent step. The issue payload carries no executor, so the CLI
|
|
132
|
+
goes by the team: agent steps are filed in the organization's Operator team
|
|
133
|
+
(named Operator, keyed `OPERATOR`, or `OPERATOR2` and so on). That is a
|
|
134
|
+
heuristic. A step handed back to a person keeps its team and stays marked,
|
|
135
|
+
and the claim itself is the authority.
|
|
136
|
+
|
|
137
|
+
**Claim the next issue.** `harness ready --claim` is a single request: the
|
|
138
|
+
server picks the team's most important ready issue that has no assignee
|
|
139
|
+
(urgent, high, medium, low, then none; oldest first, then by id), assigns
|
|
140
|
+
you, and moves it to the team's first started state, all in one transaction.
|
|
141
|
+
Concurrent callers get different issues, and the CLI does no paging, sorting,
|
|
142
|
+
or retrying of its own.
|
|
143
|
+
|
|
144
|
+
- It needs a team: `--team`, then `HARNESS_TEAM`, then the repo config. With
|
|
145
|
+
none of them it is a usage error, and the claim is never sent.
|
|
146
|
+
- `--all`, `--limit`, and `--page` are refused with `--claim`, because the
|
|
147
|
+
server already ranks the whole ready set.
|
|
148
|
+
- An empty queue is a normal outcome: it prints "Nothing to claim: team CLI
|
|
149
|
+
has no unassigned ready issues." (`null` with `--json`) and exits `0`.
|
|
150
|
+
- A rate limit, server error, or unreadable response exits `1`, and an
|
|
151
|
+
unreachable API exits `3`. None of these is reported as an empty queue.
|
|
152
|
+
The CLI never retries the claim. When an unreadable response or a lost
|
|
153
|
+
connection leaves it unknown whether the claim landed, the error says to
|
|
154
|
+
check `harness list --team <key> --assignee me --status in_progress`
|
|
155
|
+
before claiming again.
|
|
75
156
|
|
|
76
157
|
**Read.** `harness show <id>` prints the fields, relations, children, and
|
|
77
158
|
comments. It also downloads the images attached to the issue's description,
|
|
@@ -79,9 +160,27 @@ design, acceptance criteria, notes, and comments (up to 20, cached locally)
|
|
|
79
160
|
and prints their paths. Screenshots are often the reproduction, so read
|
|
80
161
|
them. `--no-images` skips the downloads. If a download fails, a stderr
|
|
81
162
|
notice says so and the command still succeeds. `harness comments <id>`
|
|
82
|
-
prints the full, untruncated comment thread with comment ids.
|
|
163
|
+
prints the full, untruncated comment thread with comment ids. The
|
|
164
|
+
`Blocked:` line names a cancelled or archived blocker as such
|
|
165
|
+
(`yes, by CLI-3 (cancelled), CLI-4 (archived), CLI-5`). When the issue has
|
|
166
|
+
wait conditions, `Waiting:` says how many still hold it, and a
|
|
167
|
+
`Wait conditions:` section lists each one (`[ ] gate "Finance sign-off" · <id>`,
|
|
168
|
+
`[ ] gate "Compliance sign-off" [compliance] owned by Compliance officer (role:compliance_officer) · <id>`,
|
|
169
|
+
`[x] not before … (satisfied …)`, `system event`) with the key and id
|
|
170
|
+
`harness gate satisfy` takes.
|
|
83
171
|
`harness activity <id>` prints the issue's activity timeline: comments,
|
|
84
|
-
creation,
|
|
172
|
+
creation, state and assignee changes, wait-condition events
|
|
173
|
+
(`Ada satisfied the gate "Finance sign-off"`), and field changes
|
|
174
|
+
(`changed priority from High to Urgent`, `renamed the issue from “…” to “…”`,
|
|
175
|
+
`edited the description`, `added label regression and removed label idea`,
|
|
176
|
+
`set the parent to CLI-3`, `archived the issue`). A description edit records
|
|
177
|
+
only that it happened, never the text. When a write was made under an agent
|
|
178
|
+
or CLI label, the person comes first and the label follows in quotes:
|
|
179
|
+
`Ada via “Claude Code” moved the issue from Backlog to In progress`. A field
|
|
180
|
+
this CLI does not know yet prints as `changed the <field> field`; `--json`
|
|
181
|
+
passes every entry through as the server sent it. A server that does not
|
|
182
|
+
record field changes yet refuses to serve them; the timeline then comes
|
|
183
|
+
without them, and a stderr notice says so. It shows the newest 20 entries
|
|
85
184
|
(`--limit` accepts 1–100), and `--all` reads further back.
|
|
86
185
|
|
|
87
186
|
**Create.** Creating an issue before you write code keeps the work tracked:
|
|
@@ -110,8 +209,13 @@ harness create --title "Fix login redirect" --type bug --priority high \
|
|
|
110
209
|
**Update.** `harness update <id>` with:
|
|
111
210
|
|
|
112
211
|
- `--claim`: atomic. It assigns you and moves the issue to the team's first
|
|
113
|
-
started state. A conflict
|
|
114
|
-
|
|
212
|
+
started state. A conflict (exit `1`, HTTP 409) names its reason: someone
|
|
213
|
+
else holds the issue, or it is not claimable yet. Two reasons come with a
|
|
214
|
+
next step: `agent_step` (Operator runs it; to do it yourself, set its
|
|
215
|
+
executor to `none` with `harness plan edit <plan-id>` and release the plan
|
|
216
|
+
again) and `waiting_on_condition` (`harness show` lists what it waits on;
|
|
217
|
+
tick a gate with `harness gate satisfy`). It cannot be combined with
|
|
218
|
+
`--assignee`.
|
|
115
219
|
- `--state <name-or-type>`: moves the issue to the workflow state whose name
|
|
116
220
|
or type matches.
|
|
117
221
|
- `--title`, `--description`, `--design`, `--acceptance`, `--priority`,
|
|
@@ -136,7 +240,13 @@ duplicate-of form records a duplicate relation and cannot be combined with
|
|
|
136
240
|
`--cancel`. `--reason` records why the issue is done. The output lists the
|
|
137
241
|
issues each closure unblocked
|
|
138
242
|
(`Closed CLI-12 — unblocked: CLI-13, CLI-14`); act on them instead of
|
|
139
|
-
leaving them in the queue.
|
|
243
|
+
leaving them in the queue. Only completing releases dependents: a canceled
|
|
244
|
+
issue still blocks them. A duplicate close moves its blocking edges to the
|
|
245
|
+
canonical issue, and any edge that cannot move stays on the duplicate. When
|
|
246
|
+
the closed issue still blocks anything, an indented line names those
|
|
247
|
+
dependents (`Still blocking: CLI-13 (a cancelled issue still blocks its
|
|
248
|
+
dependents)`) so you can remove or re-point those dependencies. With
|
|
249
|
+
several ids, each one is closed on its own.
|
|
140
250
|
A failure prints `FAILED <id>: …`, and the command exits `1` after trying
|
|
141
251
|
the rest. `harness archive <id>…` archives issues, `--restore` brings them
|
|
142
252
|
back, and `harness list --scope archived` lists them.
|
|
@@ -167,8 +277,12 @@ comments. Comment ids are UUIDs from `harness comments`.
|
|
|
167
277
|
`--parent <id>` (children of an epic), and `--scope all|archived`.
|
|
168
278
|
|
|
169
279
|
**Overview.** `harness stats` prints per-team and total counts.
|
|
170
|
-
`harness prime` prints those counts, the
|
|
171
|
-
|
|
280
|
+
`harness prime` prints those counts, the organization's ten most important
|
|
281
|
+
ready issues across every team (urgent first and priority none last, ranked
|
|
282
|
+
the way `harness ready --claim` ranks, though that claims only unassigned
|
|
283
|
+
issues on one team), and a short workflow reminder. It is meant for the
|
|
284
|
+
start of an agent session. Against a server older than this read it shows
|
|
285
|
+
the ten newest ready issues instead.
|
|
172
286
|
|
|
173
287
|
### Text from a file
|
|
174
288
|
|
|
@@ -193,7 +307,9 @@ git log -1 --format=%B | harness close CLI-12 --reason-file -
|
|
|
193
307
|
| `comment`, `comment edit` | `--body-file` |
|
|
194
308
|
|
|
195
309
|
Other commands (`project`, `milestone`, `doc`, `link`, and so on) take inline
|
|
196
|
-
values only.
|
|
310
|
+
values only. `plan create` and `plan edit` read a whole JSON or YAML
|
|
311
|
+
document with `--file` (see [Plans](#plans)), under the same stdin and
|
|
312
|
+
UTF-8 rules, up to 8 MB (the API's request limit). `-` reads from stdin, and only one option per invocation can do
|
|
197
313
|
that. stdin must not be a terminal. Passing both the inline and the file form
|
|
198
314
|
of a field is a usage error, and so is a file that produces an empty title,
|
|
199
315
|
reason, or comment. Conflicting options are caught before anything is read,
|
|
@@ -201,6 +317,524 @@ so a rejected command never consumes your pipe. A file must be valid UTF-8
|
|
|
201
317
|
and at most 1 MB, and one trailing newline is dropped, as with
|
|
202
318
|
`$(cat file)`.
|
|
203
319
|
|
|
320
|
+
## Plans
|
|
321
|
+
|
|
322
|
+
A plan is a root issue and its steps, created in one request: their
|
|
323
|
+
parents, `blocked_by` dependencies, executors, teams and wait conditions.
|
|
324
|
+
Either the whole plan is created or nothing is. Plans start held, so
|
|
325
|
+
nothing runs until a person releases one.
|
|
326
|
+
|
|
327
|
+
```sh
|
|
328
|
+
harness plan create --file plan.yaml --team IO --idempotency-key q3-report
|
|
329
|
+
harness plan show <plan-id>
|
|
330
|
+
harness plan release <plan-id> --collaborator grace@example.com
|
|
331
|
+
harness gate satisfy IO-121 "Finance sign-off"
|
|
332
|
+
harness gate satisfy IO-121 compliance # by the gate's key
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
**The plan file** is the API's request body, as JSON (`.json`) or YAML
|
|
336
|
+
(anything else, and stdin with `--file -`). YAML is read as YAML 1.2 and
|
|
337
|
+
held to what JSON can carry: dates stay strings, and a duplicate key, an
|
|
338
|
+
explicit tag such as `!!timestamp`, or `.inf`/`.nan` is refused. The
|
|
339
|
+
root's fields sit at the top level, and each step has a `key` that other
|
|
340
|
+
steps refer to:
|
|
341
|
+
|
|
342
|
+
```yaml
|
|
343
|
+
title: Ship the Q3 LP report
|
|
344
|
+
spend_budget_cents: 5000 # needed to release Operator steps
|
|
345
|
+
steps:
|
|
346
|
+
- key: gather
|
|
347
|
+
title: Gather the fund data
|
|
348
|
+
executor: operator # Operator runs it; the description is its instruction
|
|
349
|
+
description: Pull the Q3 NAVs for every fund in the portfolio
|
|
350
|
+
- key: review
|
|
351
|
+
title: Review the numbers
|
|
352
|
+
team: IO # a person's step; defaults to --team
|
|
353
|
+
blocked_by: [gather]
|
|
354
|
+
conditions:
|
|
355
|
+
- kind: gate
|
|
356
|
+
label: Finance sign-off
|
|
357
|
+
- gate: compliance # a gate with this key; the label defaults to it
|
|
358
|
+
label: Compliance sign-off
|
|
359
|
+
owner: role:compliance_officer
|
|
360
|
+
- key: send
|
|
361
|
+
title: Send the report
|
|
362
|
+
blocked_by: [review]
|
|
363
|
+
conditions:
|
|
364
|
+
- kind: not_before
|
|
365
|
+
not_before: "2026-10-15T09:00:00Z"
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
- A step takes the issue fields (`title`, `description`, `priority` as a
|
|
369
|
+
number from 0 for none to 4 for low, `assignee`, `label_ids`, …) plus
|
|
370
|
+
`key`, `parent` (the key of the step that contains it), `blocked_by`
|
|
371
|
+
(keys), `executor` (`none`, the default, or `operator`), `team`, and
|
|
372
|
+
`conditions`. Operator steps are filed in the Operator team. Only steps
|
|
373
|
+
without sub-steps carry an executor, edges, or conditions.
|
|
374
|
+
- Wait conditions: `not_before` (a UTC instant), `gate` (a `label` a person
|
|
375
|
+
ticks), and `event` (`event`, `subject_id`, and for a stage change
|
|
376
|
+
`target_id`). The allowed events are `deal_stage_changed`,
|
|
377
|
+
`opportunity_stage_changed`, `form_submitted`, and `investment_executed`.
|
|
378
|
+
Every condition must hold before the step is ready.
|
|
379
|
+
- A gate can carry a `key`, a lowercase slug (`a-z`, `0-9` and `-`) unique
|
|
380
|
+
among the step's gates, which `gate satisfy` accepts. `gate: <key>` is
|
|
381
|
+
shorthand for `kind: gate` with that key, labelled with the key unless
|
|
382
|
+
`label` is given. A gate can also have an `owner`: `role:<role>` (one of
|
|
383
|
+
the fund-team roles `gp`, `compliance_officer`, `valuer`, `accountant`,
|
|
384
|
+
`auditor` and `counsel`), `user:<id or email>` (a member who can use
|
|
385
|
+
Operator), or `expert:<email>` (an outside expert, never a member's
|
|
386
|
+
email). Only the owner can tick an owned gate. You can't name yourself as
|
|
387
|
+
an owner, and that includes a role you hold. `owner: null` or no owner
|
|
388
|
+
leaves the gate unowned.
|
|
389
|
+
- A plan has at most 100 steps, its root included.
|
|
390
|
+
- `--team` (or `HARNESS_TEAM` or the repo config) is the root's team and the
|
|
391
|
+
default team for people's steps. `--idempotency-key` works as it does for
|
|
392
|
+
`create`: if a create times out, run it again with the same key to get
|
|
393
|
+
the same plan back.
|
|
394
|
+
|
|
395
|
+
**Validation.** The server checks the whole graph and an invalid plan
|
|
396
|
+
creates nothing. The command exits `1`, and stderr lists every violation by
|
|
397
|
+
the step key it is about:
|
|
398
|
+
|
|
399
|
+
```text
|
|
400
|
+
harness: The plan is not valid; nothing was changed (HTTP 422)
|
|
401
|
+
step "review" (steps[1]): parent names no step of this plan, or no issue in this organization (parent: nope) [unknown_parent]
|
|
402
|
+
plan: A plan with Operator steps needs a positive spend budget [spend_budget_required]
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
A gate's owner is checked the same way. The owner violations are
|
|
406
|
+
`invalid_owner`, `unknown_role`, `unknown_owner`, `owner_is_member`,
|
|
407
|
+
`owner_is_actor` (you named yourself), `owner_not_allowed` (an owner on a
|
|
408
|
+
condition that is not a gate), `expert_owner_requires_plan`, and
|
|
409
|
+
`duplicate_gate_key`. A `gate:` that conflicts with the condition's `kind`
|
|
410
|
+
or `key` is `invalid_condition`.
|
|
411
|
+
|
|
412
|
+
**Show.** `harness plan show <plan-id>` prints the plan's status, budgets,
|
|
413
|
+
revision, and spend estimate, then every step in tree order (indented by
|
|
414
|
+
depth) with its key, executor, state, and status. The status is `ready`,
|
|
415
|
+
`held` (edited since the release), `not in revision` (added after it),
|
|
416
|
+
`waiting` (on a wait condition), `container`, `done`, `cancelled`,
|
|
417
|
+
`archived`, or `-`. The edges and each step's wait conditions come after
|
|
418
|
+
the steps. Each gate shows its key and owner. A `Waiting for:` section then
|
|
419
|
+
lists the open gates each step waits for, with the gate's owner and the
|
|
420
|
+
people who can tick it. When nobody can, it says `nobody can tick`:
|
|
421
|
+
|
|
422
|
+
```text
|
|
423
|
+
Waiting for:
|
|
424
|
+
IO-121 "Compliance sign-off" [compliance] · Compliance officer (role:compliance_officer) · can tick: Ada Lovelace, Grace Hopper (expert, Acme LLP)
|
|
425
|
+
IO-122 "Legal review" [legal] · Bob Smith (user:7d3c2a1e-5b4f-4c8d-9e0a-1f2b3c4d5e6f) · nobody can tick
|
|
426
|
+
IO-123 "Smoke test" · no owner, anyone with standing can tick
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
An owner shows as its name followed by how the server names it: a role
|
|
430
|
+
(`Compliance officer (role:compliance_officer)`), a member by user id
|
|
431
|
+
(`Bob Smith (user:<uuid>)`, even when the plan document named them by
|
|
432
|
+
email), or an outside expert by email (`val@example.com (expert)`).
|
|
433
|
+
|
|
434
|
+
`--json` prints the plan as the server sends it, including each step's
|
|
435
|
+
`waiting_for` (`condition_id`, `key`, `label`, `owner`, and `holders`:
|
|
436
|
+
`[]` when nobody can tick, `null` for an unowned gate or when the holders
|
|
437
|
+
are not known).
|
|
438
|
+
|
|
439
|
+
**Edit.** `harness plan edit <plan-id> --file edits.yaml` changes step
|
|
440
|
+
executors and keys, naming each step by its `step` key or `issue`
|
|
441
|
+
identifier, and the budgets (`retry_budget`, `spend_budget_cents`):
|
|
442
|
+
|
|
443
|
+
```yaml
|
|
444
|
+
steps:
|
|
445
|
+
- step: gather
|
|
446
|
+
executor: none # hand the step to a person
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
Editing a released step holds it until the plan is released again. A
|
|
450
|
+
budget change needs the plan held first. Descriptions, parents, and edges
|
|
451
|
+
are edited with the issue commands (`update`, `dep`). Conditions are set
|
|
452
|
+
when the plan is created. Editing them later goes through the API's issue
|
|
453
|
+
condition endpoints, which have no command yet; `gate satisfy` ticks a
|
|
454
|
+
gate.
|
|
455
|
+
|
|
456
|
+
**Release and hold.** `harness plan release <plan-id>` releases the plan as
|
|
457
|
+
you: it freezes the next revision and lets ready steps run.
|
|
458
|
+
`--collaborator <id-or-email>` (repeatable) adds people to the plan room
|
|
459
|
+
where its Operator steps run, and `--retry-budget` and
|
|
460
|
+
`--spend-budget-cents` set the budgets. `harness plan hold <plan-id>`
|
|
461
|
+
pauses every step until the plan is released again.
|
|
462
|
+
|
|
463
|
+
**Gates.** `harness gate satisfy <issue> <gate>` ticks a gate and
|
|
464
|
+
`harness gate unsatisfy <issue> <gate>` unticks it. `<gate>` is the gate's
|
|
465
|
+
key, its label, or its condition id, all shown by `harness show`. None of
|
|
466
|
+
the three outranks the others: when `<gate>` exactly matches more than one
|
|
467
|
+
condition, say one gate's key and another gate's label, the command
|
|
468
|
+
refuses and lists each one's id, key, and label instead of choosing. Pass
|
|
469
|
+
the condition id of the one you mean. Only when nothing matches exactly
|
|
470
|
+
does a label or key match ignoring case, and that too is refused when more
|
|
471
|
+
than one gate matches. Ticking a gate requires staff standing in the
|
|
472
|
+
organization or a place in its plan's room. Only the owner can tick an
|
|
473
|
+
owned gate, and only its owner or Harness staff can untick it. Anyone else
|
|
474
|
+
gets an error that names the gate's owner as of the refusal, which the
|
|
475
|
+
server sends with it, and exits `1`:
|
|
476
|
+
|
|
477
|
+
```text
|
|
478
|
+
harness: Cannot satisfy gate "Compliance sign-off" on IO-121: only its owner, Compliance officer (role:compliance_officer), can tick it (HTTP 403)
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
A gate with no owner by then gets the same error without a name. Against
|
|
482
|
+
a server that does not send the owner, the command reads the conditions
|
|
483
|
+
again to find it.
|
|
484
|
+
|
|
485
|
+
**Conditions.** `harness conditions <issue>` lists an issue's wait
|
|
486
|
+
conditions, oldest first, one per line as `show` lists them: each one's
|
|
487
|
+
box, what it waits for (a gate's label, key and owner), when and by whom it
|
|
488
|
+
was satisfied, and its id. `--json` is the list as the server sends it.
|
|
489
|
+
|
|
490
|
+
A server without the plan API answers the plan commands with a 404, and the
|
|
491
|
+
error says that the server may predate plans.
|
|
492
|
+
|
|
493
|
+
## Outside experts
|
|
494
|
+
|
|
495
|
+
An outside expert invited to a plan can hold a `pxc_…` credential. The
|
|
496
|
+
expert mints it in their own expert view, with a fresh code from their
|
|
497
|
+
authenticator app, and it lasts at most 30 days. It reaches only:
|
|
498
|
+
|
|
499
|
+
- their assignments (`GET /api/v1/expert/assignments`);
|
|
500
|
+
- on a step assigned to them, the gates they own: listed, ticked and
|
|
501
|
+
unticked.
|
|
502
|
+
|
|
503
|
+
To the CLI it is an ordinary bearer credential. In a terminal, paste it
|
|
504
|
+
into `harness login --no-browser`, which verifies it against the
|
|
505
|
+
assignments and stores it like any login. For an agent or a script, export
|
|
506
|
+
it as `HARNESS_API_TOKEN`. Writes are attributed to the expert's own
|
|
507
|
+
account either way.
|
|
508
|
+
|
|
509
|
+
```sh
|
|
510
|
+
harness login --no-browser # paste the pxc_ credential
|
|
511
|
+
harness whoami # the expert's role and plan
|
|
512
|
+
harness assignments # their steps, and the gates they own on each
|
|
513
|
+
harness conditions IO-130 # their gates on one step
|
|
514
|
+
harness gate satisfy IO-130 valuation # tick one, by key, label or id
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
`harness whoami` says the credential is an outside expert's and names the
|
|
518
|
+
expert's role, the plan and how many steps are assigned to them:
|
|
519
|
+
|
|
520
|
+
```text
|
|
521
|
+
Host: https://harness.infra.one (via stored login)
|
|
522
|
+
Credential: stored login, an outside expert's pxc_ credential
|
|
523
|
+
Expert: Valuer on plan 0b6f5e9a-6c1e-4d3f-9a39-2f1f8f7e4c11 (released) · 1 assigned step
|
|
524
|
+
Reaches: its own assignments and the gates it owns on them (assignments, conditions, gate)
|
|
525
|
+
Verified: ok (server API 1.4.693)
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
`whoami --json` always carries `expert`: `null` for any other credential,
|
|
529
|
+
and for a `pxc_…` one `{id, role, plan: {id, status} | null,
|
|
530
|
+
assigned_steps}`, beside the usual `url`, `source`, `verified`,
|
|
531
|
+
`server_version` and `host_source`.
|
|
532
|
+
|
|
533
|
+
`harness assignments` lists each step assigned to the expert that they
|
|
534
|
+
still hold, in identifier order: its state, due date, how many open issues
|
|
535
|
+
block it, the gates on it they own, and its description (left out for a
|
|
536
|
+
step Operator runs). The expert view never names a member: a gate shows its
|
|
537
|
+
owner as a role, or `you` for the expert's own gate, and who ticked it as
|
|
538
|
+
`you`, the role it was ticked as, `an outside expert` or `a member`.
|
|
539
|
+
`--json` is the server's data unchanged: `expert` (`{id, role}`), `plan`
|
|
540
|
+
(`{id, status}` or `null`) and `assignments`, each with `identifier`,
|
|
541
|
+
`title`, `description`, `state` (`{name, type}`), `due_date`,
|
|
542
|
+
`open_blockers` and `gates`. Any other credential gets a 403
|
|
543
|
+
(`expert_credential_required`).
|
|
544
|
+
|
|
545
|
+
`harness conditions <issue>` and `harness gate satisfy|unsatisfy` read and
|
|
546
|
+
tick only the expert's own gates. After a tick the CLI counts only the
|
|
547
|
+
expert's own gates still open, because the step's other conditions are out
|
|
548
|
+
of the expert's view. A step that isn't assigned to the expert answers 404,
|
|
549
|
+
which the error says.
|
|
550
|
+
|
|
551
|
+
**Every other command that reads or changes Harness or Operator data is
|
|
552
|
+
refused** with a 403 `expert_bound_credential`, reads and writes alike.
|
|
553
|
+
(`whoami`, `version`, `login` and `logout` keep working: they use only the
|
|
554
|
+
routes the credential reaches.) The CLI says what the credential reaches
|
|
555
|
+
and exits `1`:
|
|
556
|
+
|
|
557
|
+
```text
|
|
558
|
+
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)
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
The platform reads the credential, the expert's seat and the account again
|
|
562
|
+
on every request. An expired or revoked credential, a revoked or ended
|
|
563
|
+
seat, a suspended account or a changed authenticator answers 401 at once.
|
|
564
|
+
With a `pxc_…` credential the error lists those causes, and the command
|
|
565
|
+
exits `1`. If the seat is still open, mint a new credential in the expert
|
|
566
|
+
view. `harness logout` forgets a stored `pxc_…` credential without
|
|
567
|
+
revoking it, because the session route refuses it; revoke it in the expert
|
|
568
|
+
view to end it at once. Storing a `pxc_…` credential replaces (and
|
|
569
|
+
revokes) a stored member login, so log in again to use the other
|
|
570
|
+
commands.
|
|
571
|
+
|
|
572
|
+
## Operator jobs
|
|
573
|
+
|
|
574
|
+
`jobs`, `job` and `watch` read the Operator jobs you see in the Chat Mode
|
|
575
|
+
workbench, in the organization your credential belongs to. They need the
|
|
576
|
+
`operator:read` scope (a browser login carries it) and an admin or member
|
|
577
|
+
role there.
|
|
578
|
+
|
|
579
|
+
```sh
|
|
580
|
+
harness jobs --status waiting --mine
|
|
581
|
+
harness job <job-id>
|
|
582
|
+
harness watch <job-id>
|
|
583
|
+
```
|
|
584
|
+
|
|
585
|
+
Everything is by reference: states, codes, counts, ids and workbench links.
|
|
586
|
+
None of them shows a job's objective text, because the API never returns it:
|
|
587
|
+
it can quote what Operator read. Each job shows its `objective_kind` and the
|
|
588
|
+
workbench link where its objective can be read instead. Approval happens in the
|
|
589
|
+
app. No command approves anything; they print the approval link.
|
|
590
|
+
|
|
591
|
+
- `harness jobs` lists jobs newest first, with their state, what they wait
|
|
592
|
+
on, kind, owner and plan step, followed by the approval link of each job
|
|
593
|
+
waiting for approval. `--status` takes comma-separated job states (`draft`,
|
|
594
|
+
`planning`, `ready`, `running`, `waiting_for_approval`,
|
|
595
|
+
`waiting_for_human`, `paused`, `failed`, `needs_reconciliation`,
|
|
596
|
+
`completed`, `cancelled`) or the aliases `open` (not done), `waiting` and
|
|
597
|
+
`done`. `--mine` lists only your own jobs. Pages hold 20 jobs (`--limit`,
|
|
598
|
+
at most 50). A page can come back short, or even empty, while older jobs
|
|
599
|
+
exist, because the server drops the jobs you may not see after reading
|
|
600
|
+
the page. stderr says so. `--all` follows every page, 50 jobs each unless
|
|
601
|
+
`--limit` says otherwise, for up to 100 pages; past that it stops, exits
|
|
602
|
+
`1` and asks you to narrow the list with `--status` or `--mine`.
|
|
603
|
+
- `harness job <job-id>` shows the job's status, plan version, progress,
|
|
604
|
+
remaining window and budget, spend, the change set waiting for approval
|
|
605
|
+
with its link, the grant, verification, effect counts, blockers,
|
|
606
|
+
artifacts, pinned skill, people, saved procedures, and what you can do
|
|
607
|
+
in the workbench.
|
|
608
|
+
- `harness job <job-id> --changes` shows the job's latest change set
|
|
609
|
+
instead, by reference: its id, version, status, digest and plan version,
|
|
610
|
+
and each action's type, version, order, effect count, required
|
|
611
|
+
capabilities and digest, with the approval link. Values and targets never
|
|
612
|
+
leave the app; review them there. A job that has proposed nothing prints
|
|
613
|
+
"No pending change set" (`change_set` is `null` under `--json`).
|
|
614
|
+
- `harness job <job-id> --people` shows who is on the job, by name: the
|
|
615
|
+
requester, the execution principal (the person it runs as), the owner,
|
|
616
|
+
the approvers and the collaborators. A person the job doesn't name shows
|
|
617
|
+
as their user id; the API never sends emails. Pass both flags to see
|
|
618
|
+
both, and `--json` returns them under the job's id, state, link and
|
|
619
|
+
approval link.
|
|
620
|
+
- `harness watch <job-id>` prints the job's events and state changes as
|
|
621
|
+
they happen, polling every 2 seconds, until the job completes, fails or
|
|
622
|
+
is cancelled, or a person has to act. A person has to act exactly when
|
|
623
|
+
the job's `waiting_on` is set: an approval, a person, reconciliation,
|
|
624
|
+
verification or delivery. `watch` stops there with the link to act on:
|
|
625
|
+
the approval link when the job waits for approval. A job paused with
|
|
626
|
+
nothing for a person to do, such as one whose deliveries are still
|
|
627
|
+
sending, resumes by itself, so `watch` keeps following it, every 10
|
|
628
|
+
seconds. `--through-waits` follows through a person's waits too, also
|
|
629
|
+
every 10 seconds. `--after <seq>` starts after an event sequence instead
|
|
630
|
+
of replaying the history. `--json` prints one document when the watch
|
|
631
|
+
ends, with the outcome, the job's last state, the events read (ids, codes,
|
|
632
|
+
numbers and booleans only), and `next_after` to resume from.
|
|
633
|
+
- `watch` only reads, so it retries a failed poll three times (after 1, 2
|
|
634
|
+
and 4 seconds, or longer when the server sends `Retry-After`) when the API
|
|
635
|
+
is unreachable, answers with a 5xx, or rate-limits. If the poll still
|
|
636
|
+
fails, or you press Ctrl-C, stderr names the `--after` to resume from, and
|
|
637
|
+
`--json` still prints the document with the events read so far. When the
|
|
638
|
+
reader of its output goes away (`watch <id> | head -1`), it stops polling
|
|
639
|
+
and exits quietly.
|
|
640
|
+
|
|
641
|
+
`watch` exits `0` when the job completed, `4` when a person has to act,
|
|
642
|
+
`5` when the job failed or was cancelled, and `130` on Ctrl-C (see
|
|
643
|
+
[Scripting contract](#scripting-contract)).
|
|
644
|
+
|
|
645
|
+
A 403 names why the server refused: Operator switched off for the
|
|
646
|
+
organization (`operator_disabled`), no admin or member role there
|
|
647
|
+
(`operator_not_permitted`), a credential that acts as no person
|
|
648
|
+
(`actor_required`), or a missing `operator:read` scope (`forbidden`). A job
|
|
649
|
+
that does not exist, is in another organization, or is not shared with you
|
|
650
|
+
answers the same 404.
|
|
651
|
+
|
|
652
|
+
## Steering, cancelling and pausing a job
|
|
653
|
+
|
|
654
|
+
`steer`, `cancel` and `pause` change or stop an Operator job the way the
|
|
655
|
+
workbench's controls do, under the same rules and with your current
|
|
656
|
+
authority. They need the `operator:write` scope (a browser login carries it)
|
|
657
|
+
and an admin or member role in the organization.
|
|
658
|
+
|
|
659
|
+
```sh
|
|
660
|
+
harness steer <job-id> "Only use the audited Q3 figures"
|
|
661
|
+
harness steer <job-id> --instruction-file instruction.md # "-" reads stdin
|
|
662
|
+
harness cancel <job-id>
|
|
663
|
+
harness pause <job-id>
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
- `harness steer <job-id> <instruction>` posts the instruction in the job's
|
|
667
|
+
room, attributed to you, wherever the app offers Steer. The job's owner
|
|
668
|
+
and collaborators may steer. Work the instruction changes needs a fresh
|
|
669
|
+
approval: earlier approvals stop applying to it. Steering a running job
|
|
670
|
+
stops its current run so it can take the instruction. An instruction is 1
|
|
671
|
+
to 10,000 characters once trimmed, and is sent trimmed; a longer or blank
|
|
672
|
+
one is refused before anything is sent (exit `2`). Quote it: unquoted
|
|
673
|
+
words that look like options (`--json`, `--help`) are read as options, not
|
|
674
|
+
text. Put `--` before an instruction that starts with `-`, or pass it with
|
|
675
|
+
`--instruction-file <path>` (`-` for stdin), which takes any text as it
|
|
676
|
+
is. Words and `--instruction-file` together are a usage error.
|
|
677
|
+
- `harness cancel <job-id>` ends the job. Effects already committed stay
|
|
678
|
+
committed, and the command prints their counts; staged effects that never
|
|
679
|
+
ran count as unknown.
|
|
680
|
+
- `harness pause <job-id>` holds a running job for a person without
|
|
681
|
+
resetting its active-time budget. Continue it to run again.
|
|
682
|
+
|
|
683
|
+
Only the job's execution principal may cancel or pause it. Each command
|
|
684
|
+
prints the job as `job` shows it afterwards (`--json`: the same document),
|
|
685
|
+
and approving still happens in the app.
|
|
686
|
+
|
|
687
|
+
None of them is idempotent, so the CLI sends each one once and never retries
|
|
688
|
+
it, not even after a timeout or a server error. What a failure means depends
|
|
689
|
+
on whether the request can have reached the server:
|
|
690
|
+
|
|
691
|
+
- It cannot have (the name did not resolve, the connection was refused, the
|
|
692
|
+
TLS handshake failed): nothing changed, and the command exits `3` like
|
|
693
|
+
any unreachable API.
|
|
694
|
+
- It may have (a timeout, a dropped connection, a 5xx, an answer that cannot
|
|
695
|
+
be read): the outcome is unknown. The command exits `1` and says so; check
|
|
696
|
+
`harness job <job-id>` before trying again.
|
|
697
|
+
|
|
698
|
+
The server's message comes first, followed by what to do about it:
|
|
699
|
+
|
|
700
|
+
| Status | Code | Meaning |
|
|
701
|
+
| --- | --- | --- |
|
|
702
|
+
| 400 | `bad_request` | The request carried a field the server does not accept. Nothing changed; update the CLI. |
|
|
703
|
+
| 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. |
|
|
704
|
+
| 403 | `operator_disabled`, `operator_not_permitted`, `actor_required`, `forbidden`, `run_bound_credential` | As for `jobs`, except that `forbidden` means a missing `operator:write` scope. |
|
|
705
|
+
| 404 | `not_found` | The job does not exist, is in another organization, or is not shared with you. |
|
|
706
|
+
| 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. |
|
|
707
|
+
| 409 | `reconciliation_required` | Steer: partial effects need reconciling in the app first. |
|
|
708
|
+
| 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. |
|
|
709
|
+
| 409 | `principal_inactive` | The job's execution principal no longer holds the rights to run it. Nothing changed. |
|
|
710
|
+
| 409 | `cancellation_scope_changed` | Cancel: the job's work changed while it was being cancelled. It was not cancelled; read it and decide again. |
|
|
711
|
+
| 422 | `invalid_instruction`, `validation_error` | Steer: the instruction is blank or too long, or the job's room refused it. |
|
|
712
|
+
|
|
713
|
+
## Continue and saved procedures
|
|
714
|
+
|
|
715
|
+
```sh
|
|
716
|
+
harness continue <job-id> # a new window for a job, as Continue does in the app
|
|
717
|
+
harness procedure save <job-id> --name "Monthly close" # save a verified job as a procedure
|
|
718
|
+
harness procedure show <procedure-id> # its versions and the parameters a run takes
|
|
719
|
+
harness procedure run <procedure-id> --param period=2026-09 --param count:=3
|
|
720
|
+
harness procedure run <procedure-id> --params-file params.json --version 2
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
They act with the authority, budgets and kill controls of the Chat Mode
|
|
724
|
+
workbench, and need the `operator:write` scope (a browser login carries it;
|
|
725
|
+
`procedure show` needs `operator:read`).
|
|
726
|
+
|
|
727
|
+
- `continue` opens a new audited window for a job that ran out of time or
|
|
728
|
+
waits on a person, or retries a failed run or verification, exactly as
|
|
729
|
+
Continue does in the app. Only the job's execution principal, the person
|
|
730
|
+
it runs as, may continue it. It prints the job afterwards, as `job` does.
|
|
731
|
+
- `procedure save` saves a job's recipe as a versioned procedure. Only the
|
|
732
|
+
job's owner can save it, and only once it completed with its work
|
|
733
|
+
verified. Saving again saves another procedure.
|
|
734
|
+
- `procedure show` prints the procedure's name, the job it was saved from
|
|
735
|
+
and that job's link, the current version, each version's recipe digest,
|
|
736
|
+
capability ids and action roster, and the JSON schema of the parameters a
|
|
737
|
+
run takes. It never shows a version's recipe or anything the saved job
|
|
738
|
+
read. When the schema's `scoped_inputs` require `contact_reconciliation`,
|
|
739
|
+
a run needs `--group <contact-group-id>` and `--source <uploaded-file-id>`.
|
|
740
|
+
- `procedure run` starts a new job from the procedure, as Rerun does in the
|
|
741
|
+
app. Nothing of the saved job carries over: the run has fresh inputs, your
|
|
742
|
+
own current rights, and a change set that needs its own approval, which
|
|
743
|
+
happens in the app. It prints the new job's link. `--version <n>` runs only
|
|
744
|
+
while that version is still current. `--param name=value` passes a string
|
|
745
|
+
and `--param name:=<json>` a JSON value (repeatable); `--params-file <path>`
|
|
746
|
+
(`-` for stdin) passes one JSON object instead. A bad `--param` (one
|
|
747
|
+
without `=`, a name given twice or with whitespace around it), an
|
|
748
|
+
unreadable or invalid `--params-file`, both together, a `--version` that
|
|
749
|
+
isn't a positive integer, `--version`, `--params-file`, `--group` or
|
|
750
|
+
`--source` given twice, or a `--group` or `--source` with whitespace around
|
|
751
|
+
it exits `2` before anything is sent.
|
|
752
|
+
- When the new procedure or job can't be read back as you, the answer is
|
|
753
|
+
only its id and link, for people and under `--json` alike.
|
|
754
|
+
|
|
755
|
+
**None of these writes is idempotent**, so none is ever retried
|
|
756
|
+
automatically, not after a timeout or a 5xx either: a second continue opens
|
|
757
|
+
another window, a second save saves another procedure, a second run starts
|
|
758
|
+
another job. Their exit codes say whether trying again is safe:
|
|
759
|
+
|
|
760
|
+
- `3` only when the request never reached the server (the name did not
|
|
761
|
+
resolve, the connection was refused, the TLS handshake failed). Nothing
|
|
762
|
+
changed.
|
|
763
|
+
- `1` with "Outcome unknown" when it may have: a timeout, a dropped
|
|
764
|
+
connection, an answer that could not be read (then the server received
|
|
765
|
+
it), or a 5xx. Check what the error names first (`job <id>`, the job's
|
|
766
|
+
procedures, or `jobs` for a run's new job) before trying again. A 503
|
|
767
|
+
`service_unavailable` from `procedure run` is the exception: the server
|
|
768
|
+
refused the run before starting it.
|
|
769
|
+
|
|
770
|
+
The MCP tools follow the same rule: only a request that never reached the
|
|
771
|
+
server says "cannot reach the API".
|
|
772
|
+
|
|
773
|
+
A `continue` that answers 404 may still have happened, if the job went out
|
|
774
|
+
of your sight just as it was continued: check `job <id>` first.
|
|
775
|
+
`job <id> --changes` reads the job and then its change set; when a new
|
|
776
|
+
change set lands between the two reads, stderr says so.
|
|
777
|
+
|
|
778
|
+
Every refusal exits `1` with the server's message, what it means and the
|
|
779
|
+
HTTP status:
|
|
780
|
+
|
|
781
|
+
| Status | Code | Meaning |
|
|
782
|
+
| --- | --- | --- |
|
|
783
|
+
| 400 | `bad_request` | The request carried a field the server does not accept. Nothing changed; update the CLI. |
|
|
784
|
+
| 403 | `principal_required` | `continue`: only the job's execution principal may continue it. |
|
|
785
|
+
| 403 | `owner_required` | `procedure save`: only the job's owner can save it. |
|
|
786
|
+
| 403 | `operator_disabled`, `operator_not_permitted`, `actor_required`, `forbidden` | As for every Operator command; `forbidden` here means a missing `operator:write` scope. |
|
|
787
|
+
| 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. |
|
|
788
|
+
| 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). |
|
|
789
|
+
| 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. |
|
|
790
|
+
| 422 | `not_saveable`, `validation_error` | `procedure save`: the job isn't completed and verified, or the name holds control characters. |
|
|
791
|
+
| 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. |
|
|
792
|
+
| 503 | `service_unavailable` | `procedure run`: Operator's operational profile is unavailable. No job was started. |
|
|
793
|
+
|
|
794
|
+
## Operator's catalog
|
|
795
|
+
|
|
796
|
+
What Operator can do in your organization, and the skills it has:
|
|
797
|
+
|
|
798
|
+
```sh
|
|
799
|
+
harness tools # the capabilities Operator can use now, by id
|
|
800
|
+
harness tools --domain crm # one domain's
|
|
801
|
+
harness tools domains # every domain, with how many
|
|
802
|
+
harness tools search "find a party" # by keyword, best match first
|
|
803
|
+
harness tool crm.party.search # one capability, with its whole input schema
|
|
804
|
+
harness tool crm.party.search --schema result # a read's result schema
|
|
805
|
+
harness skills # the skills assigned to Operator
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
- **Only what Operator can use now.** A capability that ships disabled or is
|
|
809
|
+
switched off for the organization never appears, so there is no
|
|
810
|
+
"enabled" column.
|
|
811
|
+
- **Kinds.** Each capability is a `read`, a `write`, or a `send` (a write
|
|
812
|
+
delivered beyond the platform, such as an email). Write and send
|
|
813
|
+
capabilities, and any proposal, always need a person's approval in the app
|
|
814
|
+
before they take effect. A capability without a kind shows as `unknown`,
|
|
815
|
+
never as a read. These commands run and approve nothing.
|
|
816
|
+
- **Pages.** The index comes 25 to a page and a search 15, with no page size
|
|
817
|
+
to choose. When there is more, stderr says how many there are in all and
|
|
818
|
+
names the next page's cursor: `--all` follows every page (up to 200), and
|
|
819
|
+
`--cursor <cursor>` reads the next one, or every page from there with
|
|
820
|
+
`--all`. `tools search` takes `--domain`, `--all`, and `--cursor` as well.
|
|
821
|
+
An empty `--domain` or `--cursor` is a usage error.
|
|
822
|
+
- **Schemas.** `tool <id>` fetches every page of the schema and prints it
|
|
823
|
+
whole. A page that is out of order, skipped, of another schema, or at odds
|
|
824
|
+
with the others' fragment count fails the command, and no schema is
|
|
825
|
+
printed. `--json` returns the capability with its `input_schema` (or
|
|
826
|
+
`result_schema`) as an object. Only a read has a result schema.
|
|
827
|
+
- **Skills** show each skill's invocation name, title, the SHA-256 of its
|
|
828
|
+
content, and its id. Their content and description are never exposed.
|
|
829
|
+
|
|
830
|
+
Operator needs the `operator:read` scope, an admin or member role in the
|
|
831
|
+
organization the credential is bound to, and Operator switched on there.
|
|
832
|
+
Login tokens carry the scope; `aone_` tokens need to be minted with it. A
|
|
833
|
+
refusal exits 1 with the server's message and what it means
|
|
834
|
+
(`operator_disabled`, `operator_not_permitted`, `actor_required`). `tool`
|
|
835
|
+
answers a capability that doesn't exist, ships disabled, or is switched off
|
|
836
|
+
with the same 404.
|
|
837
|
+
|
|
204
838
|
## Uploads
|
|
205
839
|
|
|
206
840
|
`harness upload <path>` uploads a file and prints a markdown reference to
|
|
@@ -290,7 +924,7 @@ can read.
|
|
|
290
924
|
- The `activity`, `search`, and `show` notices described above.
|
|
291
925
|
- The update notice (see [Staying current](#staying-current)).
|
|
292
926
|
- **Pagination.** `ready`, `blocked`, `list`, `project list`, and `doc list`
|
|
293
|
-
return 50 records per page by default. `--limit` changes the page size,
|
|
927
|
+
return 50 records per page by default (`ready --claim` has no pages). `--limit` changes the page size,
|
|
294
928
|
`--page` selects a page, and `--all` follows every page (pass `--all` or
|
|
295
929
|
`--page`, not both). The truncation notice goes to stderr only, so a
|
|
296
930
|
script that reads just the `--json` output cannot tell a full page from a
|
|
@@ -299,10 +933,13 @@ can read.
|
|
|
299
933
|
|
|
300
934
|
| Code | Meaning |
|
|
301
935
|
| --- | --- |
|
|
302
|
-
| `0` | Success. |
|
|
303
|
-
| `1` | The command failed. Causes include an API error (the message gives the HTTP status), a
|
|
936
|
+
| `0` | Success, including `ready --claim` finding nothing to claim. |
|
|
937
|
+
| `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. |
|
|
304
938
|
| `2` | Usage or configuration error, including "not signed in" outside a terminal. Nothing was sent. |
|
|
305
|
-
| `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. |
|
|
939
|
+
| `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. |
|
|
940
|
+
| `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. |
|
|
941
|
+
| `5` | `watch` only: the Operator job failed or was cancelled. |
|
|
942
|
+
| `130` | `watch` only: interrupted with Ctrl-C. stderr names the `--after` to resume from. |
|
|
306
943
|
|
|
307
944
|
## MCP server
|
|
308
945
|
|
|
@@ -312,14 +949,23 @@ can read.
|
|
|
312
949
|
claude mcp add --scope user harness -- npx -y @allocator-one/harness-cli mcp
|
|
313
950
|
```
|
|
314
951
|
|
|
315
|
-
|
|
316
|
-
`
|
|
952
|
+
Or register it under its Operator name. The server then reports its name
|
|
953
|
+
as `operator`, and it serves the same tools:
|
|
954
|
+
|
|
955
|
+
```sh
|
|
956
|
+
claude mcp add --scope user operator -- npx -y @allocator-one/operator mcp
|
|
957
|
+
```
|
|
958
|
+
|
|
959
|
+
Register one of the two, not both, or every tool appears twice.
|
|
960
|
+
|
|
961
|
+
With a global install, `harness mcp` (or `operator mcp`) works as the
|
|
962
|
+
command instead of the `npx` form. Any other MCP client can use the same command:
|
|
317
963
|
|
|
318
964
|
```json
|
|
319
965
|
{ "mcpServers": { "harness": { "command": "npx", "args": ["-y", "@allocator-one/harness-cli", "mcp"] } } }
|
|
320
966
|
```
|
|
321
967
|
|
|
322
|
-
The server exposes
|
|
968
|
+
The server exposes 72 tools:
|
|
323
969
|
|
|
324
970
|
| Area | Tools |
|
|
325
971
|
| --- | --- |
|
|
@@ -331,6 +977,11 @@ The server exposes 52 tools:
|
|
|
331
977
|
| Labels | `label_list`, `label_create`, `label_update`, `label_delete` |
|
|
332
978
|
| Teams and states | `team_list`, `team_create`, `team_rename`, `team_set_default`, `workflow_states`, `state_add`, `state_update`, `state_order`, `state_delete` |
|
|
333
979
|
| People | `member_list`, `member_add`, `member_set_role`, `user_list` |
|
|
980
|
+
| Plans and gates | `plan_create`, `plan_show`, `plan_edit`, `plan_release`, `plan_hold`, `gate_set`, `condition_list` |
|
|
981
|
+
| Outside experts | `assignment_list` |
|
|
982
|
+
| Operator jobs | `jobs_list`, `job_show`, `job_changes`, `job_steer`, `job_cancel`, `job_pause` |
|
|
983
|
+
| Continue and procedures | `job_continue`, `procedure_save`, `procedure_show`, `procedure_run` |
|
|
984
|
+
| Operator's catalog | `tools_search` (without `query` it browses the index; `domain`, `cursor`, `all`), `skills_list` |
|
|
334
985
|
| Other | `stats`, `upload` |
|
|
335
986
|
|
|
336
987
|
The tools cover the issue workflow and the admin surface. Some commands are
|
|
@@ -343,8 +994,48 @@ Some tools behave differently from their commands:
|
|
|
343
994
|
|
|
344
995
|
- `issue_list` with `ready: true` or `blocked: true` accepts every list
|
|
345
996
|
filter. The CLI's `ready` and `blocked` take only `--team`.
|
|
997
|
+
- `issue_list` only lists. Claiming the team's next issue (`ready --claim`)
|
|
998
|
+
is CLI-only; over MCP, claim a specific issue with `issue_claim`.
|
|
346
999
|
- `issue_show` downloads images only when you pass `images: true`.
|
|
347
1000
|
- `state_delete` and `label_delete` don't need the CLI's `--yes`.
|
|
1001
|
+
- `plan_create` takes the plan as an object (`plan`), with `team` and
|
|
1002
|
+
`idempotency_key` beside it, instead of a file. Its schema describes the
|
|
1003
|
+
steps and conditions, including a gate's `key` and `owner` and the
|
|
1004
|
+
`gate: <key>` shorthand, which makes `kind` optional. Keys inside `plan`
|
|
1005
|
+
that the schema does not know are still sent, so the server reports them. `plan_edit` takes
|
|
1006
|
+
`steps`, `retry_budget`, and `spend_budget_cents` directly. As with every
|
|
1007
|
+
tool, a top-level argument the tool does not declare is dropped.
|
|
1008
|
+
`gate_set` takes `issue`, `gate` (a key, label, or condition id), and
|
|
1009
|
+
`satisfied`.
|
|
1010
|
+
- An API error's details come back in the tool error text: a validation
|
|
1011
|
+
error's fields, and an invalid plan's violations, one per line by step
|
|
1012
|
+
key.
|
|
1013
|
+
- With an outside expert's `pxc_…` credential, `assignment_list`,
|
|
1014
|
+
`condition_list` and `gate_set` work on the expert's own steps and gates.
|
|
1015
|
+
Every other tool returns the `expert_bound_credential` refusal, with the
|
|
1016
|
+
same explanation the CLI prints.
|
|
1017
|
+
- `issue_list` with `ready: true` gives every issue an `agent_step` flag,
|
|
1018
|
+
read from its team as `ready` does.
|
|
1019
|
+
- `jobs_list` takes `status` as a list of job states and aliases. There is
|
|
1020
|
+
no watch tool: poll `job_show` until `state` is `completed`, `failed` or
|
|
1021
|
+
`cancelled`, or `waiting_on` is set. A person has to act exactly when
|
|
1022
|
+
`waiting_on` is set; a job paused without it resumes by itself. Neither
|
|
1023
|
+
tool approves anything; give a person the `approval_url`.
|
|
1024
|
+
- `job_steer`, `job_cancel` and `job_pause` return the job as `job_show`
|
|
1025
|
+
does. None is idempotent, so none is retried: after an error that leaves
|
|
1026
|
+
the outcome unknown, read the job with `job_show` before calling one
|
|
1027
|
+
again. All three carry `idempotentHint: false`. `job_cancel` sets
|
|
1028
|
+
`destructiveHint: true`; `job_steer` (it voids earlier approvals and stops
|
|
1029
|
+
a run) and `job_pause` (it stops a run) leave MCP's default, which is also
|
|
1030
|
+
destructive. Approving stays in the app.
|
|
1031
|
+
- `job_changes` is the CLI's `job --changes`. There is no tool for
|
|
1032
|
+
`job --people`: `job_show` already returns the requester, execution
|
|
1033
|
+
principal, participants and approvers.
|
|
1034
|
+
- `procedure_run` takes `parameters` as an object, beside `version`,
|
|
1035
|
+
`group_id` and `source_resource_id`. `job_continue`, `procedure_save` and
|
|
1036
|
+
`procedure_run` are never retried and carry `idempotentHint: false`; a
|
|
1037
|
+
failed call's error says what to check before calling again, and only a
|
|
1038
|
+
request that never reached the server says "cannot reach the API".
|
|
348
1039
|
|
|
349
1040
|
For example, `issue_list` with `{"ready": true, "team": "CLI"}` returns the
|
|
350
1041
|
ready queue, and `issue_claim` with `{"id": "CLI-12"}` claims an issue
|
|
@@ -397,7 +1088,7 @@ automatic login asks for confirmation first, as described above. Plain
|
|
|
397
1088
|
| Variable | Effect |
|
|
398
1089
|
| --- | --- |
|
|
399
1090
|
| `HARNESS_API_URL` | Target host (https, or http against localhost only). |
|
|
400
|
-
| `HARNESS_API_TOKEN` | An `aone_…` API token. It takes precedence over the stored login. Headless use only. |
|
|
1091
|
+
| `HARNESS_API_TOKEN` | An `aone_…` API token, or an outside expert's `pxc_…` credential. It takes precedence over the stored login. Headless use only. |
|
|
401
1092
|
| `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. |
|
|
402
1093
|
| `HARNESS_TEAM` | Default team key for commands that take `--team`. |
|
|
403
1094
|
| `HARNESS_CACHE_DIR` | Where `show` stores downloaded images. Must be an absolute path. |
|
|
@@ -413,7 +1104,7 @@ automatic login asks for confirmation first, as described above. Plain
|
|
|
413
1104
|
| `~/.config/harness/credentials.json` | The stored login: one `{url, token}` pair, mode 0600. |
|
|
414
1105
|
| `~/.config/harness/trusted-hosts.json` | Repo-config hosts you agreed to log in to. |
|
|
415
1106
|
| `~/.cache/harness/attachments/` | Images downloaded by `show` (or `HARNESS_CACHE_DIR`). |
|
|
416
|
-
| `~/.cache/harness/update-check.json` | The daily update-check stamp. |
|
|
1107
|
+
| `~/.cache/harness/update-check.json` | The daily update-check stamp (`update-check-operator.json` for `@allocator-one/operator`). |
|
|
417
1108
|
| `~/.cache/harness/upgrade.lock` | Held while an automatic upgrade runs. |
|
|
418
1109
|
|
|
419
1110
|
None of these files is tracker state: issues stay on the server. The
|
|
@@ -425,6 +1116,9 @@ longer need them.
|
|
|
425
1116
|
**Notice.** Each run reads the cached stamp and prints at most one stderr
|
|
426
1117
|
line when a newer release exists:
|
|
427
1118
|
`harness: 1.13.0 is available (you have 1.12.1) — npm i -g @allocator-one/harness-cli@latest`.
|
|
1119
|
+
The notice follows the package you installed. An `@allocator-one/operator`
|
|
1120
|
+
install checks that package's own latest release, keeps its own stamp, and
|
|
1121
|
+
is told to upgrade `@allocator-one/operator`.
|
|
428
1122
|
When the stamp is more than a day old, a detached background process
|
|
429
1123
|
refreshes it from the npm registry. A command never waits on that network
|
|
430
1124
|
request. The notice applies only to global and project-local installs, not
|
|
@@ -433,7 +1127,8 @@ to `npx` or a git checkout.
|
|
|
433
1127
|
**Automatic upgrade on HTTP 426.** The CLI sends its version with every
|
|
434
1128
|
request, and the server refuses versions below its minimum with a 426. In
|
|
435
1129
|
that case the CLI runs
|
|
436
|
-
`npm install --global --ignore-scripts @allocator-one/harness-cli@latest
|
|
1130
|
+
`npm install --global --ignore-scripts @allocator-one/harness-cli@latest`
|
|
1131
|
+
(or `@allocator-one/operator@latest`, for an install of that package),
|
|
437
1132
|
re-runs the original command, and exits with that command's exit code.
|
|
438
1133
|
Re-running is safe only for this status. The server answers 426 before
|
|
439
1134
|
authentication and before the request handler, so the refused request
|