@specset/cli 2.0.1 → 2.1.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 +15 -3
- package/dist/main.js +18727 -16925
- package/package.json +1 -1
- package/plugin/specset/.claude-plugin/plugin.json +1 -1
- package/plugin/specset/.codex-plugin/plugin.json +1 -1
- package/plugin/specset/skills/specset-admin/SKILL.md +12 -7
- package/plugin/specset/skills/specset-agent/SKILL.md +26 -8
- package/plugin/specset/skills/specset-closeout/SKILL.md +5 -2
- package/plugin/specset/skills/specset-submittals/SKILL.md +1 -1
- package/plugin/specset/skills/specset-tools/SKILL.md +3 -3
- package/skills/specset-admin/SKILL.md +12 -7
- package/skills/specset-agent/SKILL.md +26 -8
- package/skills/specset-closeout/SKILL.md +5 -2
- package/skills/specset-submittals/SKILL.md +1 -1
- package/skills/specset-tools/SKILL.md +3 -3
package/package.json
CHANGED
|
@@ -12,15 +12,15 @@ Every operation here takes your org id — get it with `{ me { orgMembers { org
|
|
|
12
12
|
|
|
13
13
|
Roles (`OrgMemberTeamRole`): `Member`, `Manager`, `Admin`. Statuses (`OrgMemberStatus`): `PendingApproval`, `Active`, `Deactivated`. You need to be an org Admin for most mutations in this skill.
|
|
14
14
|
|
|
15
|
-
List members —
|
|
15
|
+
List active members — `--role` narrows to some roles, `--json` adds each membership `id` the mutations below take:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
specset
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}' -F orgId=<org-id>
|
|
18
|
+
specset org members list --role Admin,Manager --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Search members through the API (`orgMembers(orgId:, statuses: [...])` lists members in other statuses):
|
|
23
22
|
|
|
23
|
+
```bash
|
|
24
24
|
specset api --query 'query($orgId: ID!, $search: String) {
|
|
25
25
|
paginatedOrgMembers(orgId: $orgId, search: $search, first: 20) {
|
|
26
26
|
edges { node { id role status user { email } } }
|
|
@@ -41,6 +41,10 @@ specset api --query 'mutation($id: ID!) {
|
|
|
41
41
|
}' -F id=<org-member-id>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Security Settings
|
|
45
|
+
|
|
46
|
+
`specset org security show` lists the organization's seven security settings (single sign-on, authentication requirement, passwords, passkeys, authenticator codes, onboarding policy, session length) beside the values the Secure Configuration Guide (https://docs.specset.com/docs/secure-configuration) recommends for an organization that holds federal information or CUI, each marked `matches`, `stricter` or `differs`, with the identity providers. `--json` exports the same. Any member can read them; only an Admin changes them, in the web app under **Security & SSO** (the CLI cannot). A `differs` can be deliberate, such as single sign-on required with the provider's MFA confirmed by Specset support.
|
|
47
|
+
|
|
44
48
|
## Teams
|
|
45
49
|
|
|
46
50
|
A team is a named group of members. Assigning a team to a project puts everyone on it on that project, and changes to the team carry over to every project it's on. Teams don't carry a role: people keep their org role unless a project role is set on them.
|
|
@@ -56,8 +60,9 @@ Changing a team needs org Manager or Admin and management permission in every pr
|
|
|
56
60
|
Nobody joins an org on someone else's say-so. Both paths below leave the person with a pending invitation they must accept — in the app (existing Specset users see an invite badge), by registering through the invite link, or by signing in with the org's SSO. Until then an added user is an `Invited` member: visible in pickers and assignable, but with no access.
|
|
57
61
|
|
|
58
62
|
- `createOrgMember(orgId: ID!, email: String!, firstName: String!, lastName: String!, role: ..., projectIds: [ID!], teamIds: [ID!], sendInvite: Boolean)` — adds the person as an `Invited` seat right away so they can be assigned work before they've joined. `sendInvite: true` emails the invite link; existing Specset users get the in-app invitation either way. Returns the `OrgMember` (`status: Invited`).
|
|
63
|
+
- Directory contacts are people the org tracks and assigns (a subcontractor's PM, a received-from on a submittal) who don't sign in: `createOrgMember(..., memberType: DirectoryOnly)` adds one with no invite and no access. Every member and contact can carry `companyId` (a directory company; staff default to the org's own), `jobTitle`, `phone`, `mobilePhone`, and `notes`, on create and on `updateOrgMember`. `updateOrgMember(id, role: ..., memberType: Regular, sendInvite: true)` turns a contact into an invited member. `orgMembers(orgId, companyIds: [...])` lists a company's people.
|
|
59
64
|
- `createOrgInvite(orgId: ID!, email: String!, role: ..., projectIds: [ID!])` — invite-only (no seat until they accept; no `teamIds`, so add teams once they have a seat); also used to (re-)invite an existing `Invited` seat. `projectIds` (optional, inline the list) pre-scopes a Member to specific projects. Set `role` deliberately — don't default people to more access than they need. Share the returned `inviteUrl` if the email might not land.
|
|
60
|
-
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`.
|
|
65
|
+
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`. `inviteUrl` is null unless you may re-send that invitation (a Manager or Admin, up to your own role): whoever holds the link can register as the invitee.
|
|
61
66
|
- `updateOrgInvite(id: ID!, role: ..., projectIds: [ID!])` — `role` is required.
|
|
62
67
|
- `resendOrgInvite(id: ID!)` — rotates the link and re-sends the email.
|
|
63
68
|
- `deleteOrgInvite(id: ID!)` — revoke a pending invite (an `Invited` seat stays, as "Not invited"; `deleteOrgMember` removes the seat).
|
|
@@ -97,7 +97,23 @@ Inspect `getDrawingGraphOverview` bundles and quality flags before a benchmark;
|
|
|
97
97
|
building the graph does not reprocess stale source nodes. `--autoApprovePlan`
|
|
98
98
|
allows immediate execution when the brief is already approved.
|
|
99
99
|
`agent approve --deny --reason "…"` rejects a pending proposal for revision.
|
|
100
|
-
`agent stop <threadId>`
|
|
100
|
+
`agent stop <threadId>` stops a running review and everything under it.
|
|
101
|
+
|
|
102
|
+
A follow-up message to a busy review (`agent chat --thread <threadId>`) does not
|
|
103
|
+
stop the workers it is running: it waits for them and applies at the next step,
|
|
104
|
+
beside their reports. `agent chat` then returns at once with a `held` line
|
|
105
|
+
instead of polling, and `agent show` lists the message under `queued` until the
|
|
106
|
+
review reads it. Stop is what interrupts at once. To steer a busy review, list
|
|
107
|
+
what it is running and stop single workers; the review records a stopped worker
|
|
108
|
+
as stopped and carries on, and can read the stopped worker's thread to see what
|
|
109
|
+
it finished.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
specset agent workers list <threadId> # running workers: id, status, steps, type, title
|
|
113
|
+
specset agent workers list <threadId> --all --json # finished ones too, machine-readable
|
|
114
|
+
specset agent workers stop <workerThreadId> # stop one worker; the review carries on
|
|
115
|
+
specset agent stop <threadId> # stop the review and every worker
|
|
116
|
+
```
|
|
101
117
|
|
|
102
118
|
Long-running agents — Design Review, Drawing Review, Design Quality Review, Structured Review, Submittal Log — can be started as their own session without going through a Conversation thread. `specset agent start` creates the thread with the given `--type`, titles it, seeds it with your kickoff instructions, and returns immediately with the thread id; the run continues on the server.
|
|
103
119
|
|
|
@@ -118,8 +134,9 @@ specset agent start --type DrawingReview --project <projectId> \
|
|
|
118
134
|
- `--type` accepts any top-level agent type (`DesignReview`, `DrawingReview`, `DesignQualityReview`, `StructuredReview`, `SubmittalLog`, …). The kickoff message is the review's scope: name the building areas, disciplines, sections or sheets, what to exclude, and any special coordination checks — the same text a user would type when launching the review in the app.
|
|
119
135
|
- `--type DrawingReview` also needs `--coordinationTaskTypes` (`all`, or a comma-separated subset of the 16 families: `CeilingOverhead`, `EquipmentPowerControls`, `LifeSafetyEgress`, `ArchitectureStructureOpenings`, `ArchitectureMepPenetrations`, `StructuralMepInterfaces`, `EnvelopeWaterproofing`, `FireLifeSafetyProtection`, `CivilSiteUtilities`, `ElectricalDistribution`, `MechanicalSystemsContinuity`, `PlumbingSystemsContinuity`, `StructuralSystemsContinuity`, `FireSuppressionSystemsContinuity`, `FireAlarmEmergencyCommunications`, `TechnologySecurityCommunications`). The server refuses a Drawing Review whose first message carries no scope, and the scope cannot change afterwards, so these flags apply only to new threads. `--drawingArea` limits the review to one building-scoped drawing area, and `--noDrawingArea` limits it to the sheets that belong to no drawing area (the site-wide set; exclusive with `--drawingArea`); `--coordinationTaskIds` narrows it to exact graph task ids for a bounded pilot or rerun (ids must belong to the selected families and area). `--executionIntent` decides how earlier reviews' runs of the same tasks are treated: `Reuse` (the default) answers a task from another review's completed run while the drawings it read are unchanged and leaves Blocked or Failed tasks alone (reported as incomplete), `RetryIncomplete` also re-runs those, and `Fresh` ignores earlier runs. The project needs a current drawing graph (`drawingCoordinationReviewAvailability.graphState` is `Ready`); otherwise the review cannot start.
|
|
120
136
|
- Local Drawing Review kickoff uses the same required `--coordinationTaskTypes` and optional `--coordinationTaskIds` / `--drawingArea` flags as `agent start`. Pass them with the first message to `agent serve`; an existing thread keeps its original scope.
|
|
121
|
-
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
137
|
+
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on for a new thread; a message to an existing one (`agent chat --thread`) keeps that thread's approval setting unless `--autoApprove` or `--noAutoApprove` is passed. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
122
138
|
- `--title` names the session in Agent Sessions. `agent start` is `agent chat --detach` for a new thread; use `chat --type … --timeout …` instead when you want to wait for a short run to finish.
|
|
139
|
+
- `agent show <threadId> --wait` waits for a new thread's first turn (`agent start`, `agent serve --message` without `--thread`), or for a run already in progress, to finish or stop for input, then prints it, using the same completion check as `agent chat`. A turn queued on a thread whose last turn finished still reads as done until it starts, so send follow-ups with `agent chat --thread`, which waits for its own turn. `--timeout` sets the wait in seconds (default 600). It exits non-zero when the wait times out or the run ended in an error.
|
|
123
140
|
|
|
124
141
|
## Notes
|
|
125
142
|
|
|
@@ -145,15 +162,16 @@ Codex uses `CODEX_PATH` when set, then `codex` on PATH, then the managed runtime
|
|
|
145
162
|
```bash
|
|
146
163
|
specset agent serve --host <deployment> --org <slug> & # --account claude2,codex limits it to those accounts
|
|
147
164
|
specset agent show <thread link> # the transcript so far
|
|
165
|
+
specset agent show <thread link> --wait # wait for the run to finish or stop for input
|
|
148
166
|
```
|
|
149
167
|
|
|
150
168
|
What the harness spends is recorded on the thread as unbilled local usage (per model, priced for information only) and kept out of Specset's cost and credit figures.
|
|
151
169
|
|
|
152
170
|
### Native tools and the run workspace
|
|
153
171
|
|
|
154
|
-
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting
|
|
172
|
+
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting picks the tools: Read only (reads), Accept edits (and edits), Auto or Full access (and the shell; Full asks nothing). In every mode the commands and file tools are sealed in the run's workspace: commands read nothing of the machine but the system and its developer tools (Python, git, Homebrew), write only in the workspace and a private temp directory, reach no network, and never see a credential, so your home, `~/.ssh`, the keychains, every harness's home, `~/.config`, the runner's other directories and the shared temp directories stay closed. Claude Code runs Bash in its command sandbox, which also refuses writes to `.git/config` and `.git/hooks` (git works in an existing repository, but a new one can't be created there), and a hook holds its file tools to the workspace. That hook checks a path before Claude Code opens it, so in Auto and Full a command left running in the background could race it by swapping in a symlink; the commands themselves stay sealed. Codex runs under a permissions profile the runner builds and asks for no approval, so an escalation is declined. The seal needs macOS and, for Codex, a binary the runner checked enforces the profile; elsewhere a step runs with Specset tools only. Native sub-agents stay off; Specset's own sub-agent and fan-out tools run through the same machine. Native tools apply where the harness can run them, so turning them on or off never moves a running thread to another harness. Antigravity uses Specset tools only unless it is on macOS, its `agy --version` is a checked release (1.4.2), and the owner has set `runner.antigravityNativeTools.escapeTestedVersion: "1.4.2"` in `~/.config/specset/config.yml` after the sandbox escape tests pass. A different or unreadable version turns its native tools off again. Its commands use agy's sandbox. Recursive native searches (`grep_search`, `find_by_name`) are disabled; use sandboxed command searches in Auto/Full modes. Its file tools use the same real-path check with the same concurrent-symlink race limitation as Claude Code. It launches from a private read-only project so configuration left in the shared workspace cannot run as agy hooks or MCP servers; writes to `.agents/`, `AGENTS.md` and `GEMINI.md` are also refused by file tools. Clipboard and other macOS services remain reachable from the command sandbox. The Settings menu still labels Antigravity tiers as running without native tools; this opt-in capability is checked by the runner, and helper document reads on Antigravity still run without code.
|
|
155
173
|
|
|
156
|
-
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty
|
|
174
|
+
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty and has no network, so the harness cannot download into it: project material comes through Specset tools, and the model writes what it needs from their results into the workspace. On Antigravity, attachments and file results are saved separately in private read-only input storage at the absolute paths supplied to the model; copy them into the workspace before editing. Findings cite the Specset link, never the local path.
|
|
157
175
|
|
|
158
176
|
With Native tools on and Permissions Auto or Full, the vision tier's document and drawing reads (`examineDrawingSheets`, `examineDocument`, `examineFile` and the like) may also run code to look closer at their attachments: crop a dense detail from the full-resolution file and view it enlarged. Such a read runs in its own scratch directory holding only its attachments (`inputs/attachment-N.png|pdf`), never the run workspace. Its commands are sandboxed: no network, no credentials in their environment, and nothing readable but the system, developer tools, Homebrew (for Python and Pillow) and that directory. Your own temp directory, where other applications keep files, stays closed except, under Claude Code, for the scratch files macOS's `sips` saves there; the shared `/tmp` stays closed. Claude Code's command sandbox denies reading by default, a hook holds its Read tool to the directory, and zsh skips your startup files; Codex runs under a permissions profile that adds only Homebrew and its own binary, with the core environment, so it crops with Pillow and `sips` fails; the runner first checks once that the binary enforces the profile (Codex before 0.157.1 ignores its deny entries), and otherwise runs these reads without code. Code execution needs macOS: on Linux, Claude Code's bubblewrap sandbox leaves the credentials it removes readable to commands through its own process, so these reads run without code there. Its actions are printed in the `agent serve` terminal, because a helper read's actions never reach the conversation. These reads were evaluated on the cloud models without code execution, so treat code-execution results as unevaluated.
|
|
159
177
|
|
|
@@ -167,13 +185,13 @@ Connect to an organization with `specset agent serve --host <host> --org <slug|i
|
|
|
167
185
|
|
|
168
186
|
`agent serve` uses server-owned orchestration: Temporal executes the agent and its tools, while model requests run through the connected machine. This includes generic drawing-coordination assessments, image inputs and structured output. The runner keeps a native session alive across each complete parallel tool batch and sends back the server's authorized results. All Specset approvals remain in the web app; native tools follow the thread's **Native tools** setting (see above) and native delegation stays off. Independent model requests use independent sessions. Claude uses `persistSession: false`; Codex uses `ephemeral: true`.
|
|
169
187
|
|
|
170
|
-
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for `serve`). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
188
|
+
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for a thread `serve` creates; with `--thread`, omit it to keep the thread's setting). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
171
189
|
|
|
172
|
-
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve` or `agent
|
|
190
|
+
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve`, `agent stop` or `agent workers`; these overrides do not change the CLI’s saved selection.
|
|
173
191
|
|
|
174
|
-
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
192
|
+
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. A call whose input a harness refuses for its size (Codex caps a turn's input at 1,048,576 characters) is never retried there: it moves to another harness the failover setting allows, else to its tier's cloud model when the tier falls back to the cloud, else fails naming the cause. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
175
193
|
|
|
176
|
-
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment
|
|
194
|
+
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment, and no CLI login: a native session's commands reach no network, so project material comes through Specset tools. Claude shares the selected native credential store and its refresh lock. Codex uses an authentication-only native process to refresh the selected login, then supplies temporary access tokens to isolated inference sessions. Neither copies rotating refresh credentials into job homes. Settings, hooks, plugins and global MCP servers are excluded from inference sessions. Run `claude auth login` first if the harness reports authentication is required. The harness itself still runs as the local OS user, outside the seal its commands run in. Use a dedicated account or isolated host for untrusted project content; do not share this personal runner across users.
|
|
177
195
|
|
|
178
196
|
Use `specset agent runners list --json` to inspect your registrations.
|
|
179
197
|
Use `specset agent runners revoke <runner-id>` to stop and revoke one remotely, including an offline runner after a crash.
|
|
@@ -12,6 +12,8 @@ Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, n
|
|
|
12
12
|
|
|
13
13
|
**Locations** are the spatial scaffold (site → building → level → room), nesting through `parentId`. **Systems** are disciplines and equipment groups, also nested. **Products** are specified make/models. **Assets** are installed instances: each asset has a location, may reference a product and responsible company, and can join multiple systems. **Warranties** attach to assets.
|
|
14
14
|
|
|
15
|
+
**Companies** belong to the organization's directory and are shared by its projects. A company works on a project through a **ProjectCompany** row, which holds that project's `role`, `notes`, `archived` state and closeout documentation. Assigning a directory company to a project record (an asset's responsible company, a part's vendor, a submittal's or RFI's responsible contractor) adds it to the project.
|
|
16
|
+
|
|
15
17
|
Maintenance has three layers:
|
|
16
18
|
|
|
17
19
|
- **Procedure** — reusable checklist content: ordered typed fields, descriptions, and source references.
|
|
@@ -53,6 +55,7 @@ Notable differences:
|
|
|
53
55
|
|
|
54
56
|
- Asset creation requires `projectId` and `locationId`. `paginatedAssets` accepts `search` and filters such as `statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, and `archived`.
|
|
55
57
|
- Warranty is asset-scoped; use `warrantiesByAsset(assetId)` to browse one asset's warranties.
|
|
58
|
+
- Company is org-wide: `paginatedCompanies(orgId: ...)` lists the directory and `searchCompanies(orgId, search)` finds a company by name. `paginatedProjectCompanies(projectId)` lists a project's companies with their `role`; `addCompaniesToProject(projectId, companyIds)`, `removeCompaniesFromProject(projectId, companyIds)` (refused while a project record still names the company) and `updateProjectCompany(input: { id, role, notes, archived })` manage the participation. `createCompany(input: { projectId, ... })` creates the directory company and puts it on that project.
|
|
56
59
|
- Location, Product, Company, System, and Asset updates support reversible `archived` state. Prefer archiving to deletion.
|
|
57
60
|
- Batch-delete availability varies. Introspect the mutation root rather than guessing an operation name.
|
|
58
61
|
|
|
@@ -175,7 +178,7 @@ specset api --query 'mutation { setProductDocumentRoles(productId: "<product-id>
|
|
|
175
178
|
|
|
176
179
|
Existing project Documents work too — a submittal attachment's `document { id }` (from `paginatedSubmittals … attachments { document { id title } }`) can be linked directly as `SubmittalPackage`/`Datasheet`; the only check is that the Document belongs to the same project. To bind a slice of a large file, `extractPagesFromFileResource(id: <fileResourceId>, pages: [3, 4, 5])` returns a new CloudFile to feed into `createDocumentsFromCloudFiles`. Only PDF files merge into the O&M package export; images and other types are listed as references.
|
|
177
180
|
|
|
178
|
-
Asset, Location, Product, Company, and System also have markdown documentation blocks. `update<Entity>Documentation(id, markdown)` replaces the block; `mark<Entity>DocumentationComplete(id)` attests that it is current. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
181
|
+
Asset, Location, Product, Company, and System also have markdown documentation blocks. `update<Entity>Documentation(id, markdown)` replaces the block; `mark<Entity>DocumentationComplete(id)` attests that it is current. A company's block is per project: use `updateProjectCompanyDocumentation` / `markProjectCompanyDocumentationComplete` with the ProjectCompany id. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
179
182
|
|
|
180
183
|
### Writing documentation that reads well in the O&M package export
|
|
181
184
|
|
|
@@ -324,7 +327,7 @@ For duplicate products P1 (loser) and P2 (survivor):
|
|
|
324
327
|
3. Re-run the filtered query and confirm zero remaining references.
|
|
325
328
|
4. Archive P1 with `updateProduct(input: { id: P1, archived: true })`.
|
|
326
329
|
|
|
327
|
-
Use the same impact-first pattern for duplicate companies,
|
|
330
|
+
Use the same impact-first pattern for duplicate locations and systems. Duplicate companies merge in one step: `mergeCompanies(targetId, sourceIds)` moves their projects, documents, record assignments, people and Procore links to the target, fills the target's blank profile fields from theirs (in `sourceIds` order), and archives the duplicates; it is not reversible, so confirm the pair first. `companyDuplicates(orgId)` lists likely pairs (names that differ only in case, punctuation or a legal form), and `companies(ids)` loads them side by side.
|
|
328
331
|
|
|
329
332
|
## Safety
|
|
330
333
|
|
|
@@ -18,7 +18,7 @@ Two common flows have purpose-built commands; prefer them to hand-written mutati
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
specset submittals create --project <projectId> --file ./package.pdf --json # uploads the file and creates the submittal from it; --title, repeatable --specSection <id>
|
|
21
|
-
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
21
|
+
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; exits non-zero when it times out or the review fails; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Reading
|
|
@@ -65,15 +65,15 @@ The retrieval tools return **extracted text**, which is authoritative for schedu
|
|
|
65
65
|
| Entity | Command | Also accepts | Formats / options |
|
|
66
66
|
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
67
|
| Drawing sheets | `specset drawings download 'https://app.specbook.ai/go/drawing/<uuid>'` | sheet number + `--project` | `--format image` (rendered PNG, default), `pdf` (single sheet), `file` (whole set) |
|
|
68
|
-
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | also covers RFI attachments and closeout documents
|
|
68
|
+
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | `--original` (uploaded file, not the converted PDF); also covers RFI attachments and closeout documents |
|
|
69
69
|
| Spec sections | `specset specs download 'https://app.specbook.ai/go/spec/<uuid>'` | section number (e.g. `'23 31 19'`) + `--project` | per-section PDF slice (default), `--asset book` (whole source spec file) |
|
|
70
70
|
| Submittals | `specset submittals download 'https://app.specbook.ai/go/sub/<uuid>'` | | compiled package (default when present); `--list` shows the asset inventory; `--asset attachment --index 2`, `--asset attachment --all` |
|
|
71
71
|
| RFIs | `specset rfis download 'https://app.specbook.ai/go/rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
|
|
72
|
-
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link
|
|
72
|
+
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link; `--original` (uploaded file, not the converted PDF) |
|
|
73
73
|
|
|
74
74
|
All commands support `--json` (structured `{files: [{path, bytes, ...}]}` output) and `--out` (file path, or directory for multi-file downloads). A citation passed to the wrong command errors with the right one to use.
|
|
75
75
|
|
|
76
|
-
`drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
76
|
+
`docs download`, `drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
77
77
|
|
|
78
78
|
To inspect an existing detail crop, add `--node <shortId|UUID>`, for example `specset drawings download <sheet-ref> --node d6 --format image --out detail.png`. Use `--format pdf` for the saved crop PDF. This downloads the current revision's stored crop without regenerating it; missing nodes or crops return an error. Node crops do not support `--format file`. JSON output adds the resolved `nodeId` to the normal sheet download metadata.
|
|
79
79
|
|
|
@@ -12,15 +12,15 @@ Every operation here takes your org id — get it with `{ me { orgMembers { org
|
|
|
12
12
|
|
|
13
13
|
Roles (`OrgMemberTeamRole`): `Member`, `Manager`, `Admin`. Statuses (`OrgMemberStatus`): `PendingApproval`, `Active`, `Deactivated`. You need to be an org Admin for most mutations in this skill.
|
|
14
14
|
|
|
15
|
-
List members —
|
|
15
|
+
List active members — `--role` narrows to some roles, `--json` adds each membership `id` the mutations below take:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
specset
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}' -F orgId=<org-id>
|
|
18
|
+
specset org members list --role Admin,Manager --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Search members through the API (`orgMembers(orgId:, statuses: [...])` lists members in other statuses):
|
|
23
22
|
|
|
23
|
+
```bash
|
|
24
24
|
specset api --query 'query($orgId: ID!, $search: String) {
|
|
25
25
|
paginatedOrgMembers(orgId: $orgId, search: $search, first: 20) {
|
|
26
26
|
edges { node { id role status user { email } } }
|
|
@@ -41,6 +41,10 @@ specset api --query 'mutation($id: ID!) {
|
|
|
41
41
|
}' -F id=<org-member-id>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
+
## Security Settings
|
|
45
|
+
|
|
46
|
+
`specset org security show` lists the organization's seven security settings (single sign-on, authentication requirement, passwords, passkeys, authenticator codes, onboarding policy, session length) beside the values the Secure Configuration Guide (https://docs.specset.com/docs/secure-configuration) recommends for an organization that holds federal information or CUI, each marked `matches`, `stricter` or `differs`, with the identity providers. `--json` exports the same. Any member can read them; only an Admin changes them, in the web app under **Security & SSO** (the CLI cannot). A `differs` can be deliberate, such as single sign-on required with the provider's MFA confirmed by Specset support.
|
|
47
|
+
|
|
44
48
|
## Teams
|
|
45
49
|
|
|
46
50
|
A team is a named group of members. Assigning a team to a project puts everyone on it on that project, and changes to the team carry over to every project it's on. Teams don't carry a role: people keep their org role unless a project role is set on them.
|
|
@@ -56,8 +60,9 @@ Changing a team needs org Manager or Admin and management permission in every pr
|
|
|
56
60
|
Nobody joins an org on someone else's say-so. Both paths below leave the person with a pending invitation they must accept — in the app (existing Specset users see an invite badge), by registering through the invite link, or by signing in with the org's SSO. Until then an added user is an `Invited` member: visible in pickers and assignable, but with no access.
|
|
57
61
|
|
|
58
62
|
- `createOrgMember(orgId: ID!, email: String!, firstName: String!, lastName: String!, role: ..., projectIds: [ID!], teamIds: [ID!], sendInvite: Boolean)` — adds the person as an `Invited` seat right away so they can be assigned work before they've joined. `sendInvite: true` emails the invite link; existing Specset users get the in-app invitation either way. Returns the `OrgMember` (`status: Invited`).
|
|
63
|
+
- Directory contacts are people the org tracks and assigns (a subcontractor's PM, a received-from on a submittal) who don't sign in: `createOrgMember(..., memberType: DirectoryOnly)` adds one with no invite and no access. Every member and contact can carry `companyId` (a directory company; staff default to the org's own), `jobTitle`, `phone`, `mobilePhone`, and `notes`, on create and on `updateOrgMember`. `updateOrgMember(id, role: ..., memberType: Regular, sendInvite: true)` turns a contact into an invited member. `orgMembers(orgId, companyIds: [...])` lists a company's people.
|
|
59
64
|
- `createOrgInvite(orgId: ID!, email: String!, role: ..., projectIds: [ID!])` — invite-only (no seat until they accept; no `teamIds`, so add teams once they have a seat); also used to (re-)invite an existing `Invited` seat. `projectIds` (optional, inline the list) pre-scopes a Member to specific projects. Set `role` deliberately — don't default people to more access than they need. Share the returned `inviteUrl` if the email might not land.
|
|
60
|
-
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`.
|
|
65
|
+
- `orgInvites(orgId: ID!)` / `paginatedOrgInvites(orgId: ID!, first: Int, after: String)` — pending invites; select `{ id user { email } role inviteUrl }`. `inviteUrl` is null unless you may re-send that invitation (a Manager or Admin, up to your own role): whoever holds the link can register as the invitee.
|
|
61
66
|
- `updateOrgInvite(id: ID!, role: ..., projectIds: [ID!])` — `role` is required.
|
|
62
67
|
- `resendOrgInvite(id: ID!)` — rotates the link and re-sends the email.
|
|
63
68
|
- `deleteOrgInvite(id: ID!)` — revoke a pending invite (an `Invited` seat stays, as "Not invited"; `deleteOrgMember` removes the seat).
|
|
@@ -97,7 +97,23 @@ Inspect `getDrawingGraphOverview` bundles and quality flags before a benchmark;
|
|
|
97
97
|
building the graph does not reprocess stale source nodes. `--autoApprovePlan`
|
|
98
98
|
allows immediate execution when the brief is already approved.
|
|
99
99
|
`agent approve --deny --reason "…"` rejects a pending proposal for revision.
|
|
100
|
-
`agent stop <threadId>`
|
|
100
|
+
`agent stop <threadId>` stops a running review and everything under it.
|
|
101
|
+
|
|
102
|
+
A follow-up message to a busy review (`agent chat --thread <threadId>`) does not
|
|
103
|
+
stop the workers it is running: it waits for them and applies at the next step,
|
|
104
|
+
beside their reports. `agent chat` then returns at once with a `held` line
|
|
105
|
+
instead of polling, and `agent show` lists the message under `queued` until the
|
|
106
|
+
review reads it. Stop is what interrupts at once. To steer a busy review, list
|
|
107
|
+
what it is running and stop single workers; the review records a stopped worker
|
|
108
|
+
as stopped and carries on, and can read the stopped worker's thread to see what
|
|
109
|
+
it finished.
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
specset agent workers list <threadId> # running workers: id, status, steps, type, title
|
|
113
|
+
specset agent workers list <threadId> --all --json # finished ones too, machine-readable
|
|
114
|
+
specset agent workers stop <workerThreadId> # stop one worker; the review carries on
|
|
115
|
+
specset agent stop <threadId> # stop the review and every worker
|
|
116
|
+
```
|
|
101
117
|
|
|
102
118
|
Long-running agents — Design Review, Drawing Review, Design Quality Review, Structured Review, Submittal Log — can be started as their own session without going through a Conversation thread. `specset agent start` creates the thread with the given `--type`, titles it, seeds it with your kickoff instructions, and returns immediately with the thread id; the run continues on the server.
|
|
103
119
|
|
|
@@ -118,8 +134,9 @@ specset agent start --type DrawingReview --project <projectId> \
|
|
|
118
134
|
- `--type` accepts any top-level agent type (`DesignReview`, `DrawingReview`, `DesignQualityReview`, `StructuredReview`, `SubmittalLog`, …). The kickoff message is the review's scope: name the building areas, disciplines, sections or sheets, what to exclude, and any special coordination checks — the same text a user would type when launching the review in the app.
|
|
119
135
|
- `--type DrawingReview` also needs `--coordinationTaskTypes` (`all`, or a comma-separated subset of the 16 families: `CeilingOverhead`, `EquipmentPowerControls`, `LifeSafetyEgress`, `ArchitectureStructureOpenings`, `ArchitectureMepPenetrations`, `StructuralMepInterfaces`, `EnvelopeWaterproofing`, `FireLifeSafetyProtection`, `CivilSiteUtilities`, `ElectricalDistribution`, `MechanicalSystemsContinuity`, `PlumbingSystemsContinuity`, `StructuralSystemsContinuity`, `FireSuppressionSystemsContinuity`, `FireAlarmEmergencyCommunications`, `TechnologySecurityCommunications`). The server refuses a Drawing Review whose first message carries no scope, and the scope cannot change afterwards, so these flags apply only to new threads. `--drawingArea` limits the review to one building-scoped drawing area, and `--noDrawingArea` limits it to the sheets that belong to no drawing area (the site-wide set; exclusive with `--drawingArea`); `--coordinationTaskIds` narrows it to exact graph task ids for a bounded pilot or rerun (ids must belong to the selected families and area). `--executionIntent` decides how earlier reviews' runs of the same tasks are treated: `Reuse` (the default) answers a task from another review's completed run while the drawings it read are unchanged and leaves Blocked or Failed tasks alone (reported as incomplete), `RetryIncomplete` also re-runs those, and `Fresh` ignores earlier runs. The project needs a current drawing graph (`drawingCoordinationReviewAvailability.graphState` is `Ready`); otherwise the review cannot start.
|
|
120
136
|
- Local Drawing Review kickoff uses the same required `--coordinationTaskTypes` and optional `--coordinationTaskIds` / `--drawingArea` flags as `agent start`. Pass them with the first message to `agent serve`; an existing thread keeps its original scope.
|
|
121
|
-
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
137
|
+
- `--autoApprovePlan` skips plan sign-off. To inspect a plan before execution, pass `--noAutoApprovePlan`: this also disables the CLI's default blanket tool approval, so the session parks at `finalizePlan`. Approve it with `agent approve <threadId> --tool finalizePlan`. Omitting the flag preserves the existing plan setting, but blanket approval defaults on for a new thread; a message to an existing one (`agent chat --thread`) keeps that thread's approval setting unless `--autoApprove` or `--noAutoApprove` is passed. `--isolateIssues` hides the project's pre-existing issues; `--noIsolateIssues` explicitly restores ordinary issue reads. `--reviewEffort Quick|Standard|Deep` sets a Project Review's effort: Quick investigates the strongest few leads on the root; Standard covers the brief within an approved worker budget; Deep covers the whole surveyed inventory. The plan states the expected worker count and estimated worker cost for approval. Omitting it keeps the thread's setting (Quick for a new thread). For a quick test of a Standard or Deep review, `--reviewMaxWorkers 3` caps the workers it starts (the server refuses more; the root records the rest as not examined). Native tools let a local run's harness use its own tools in the run's workspace and run sandboxed code over document and drawing reads, cropping and enlarging a detail. A thread that starts running locally has them on unless it already chose; `--noNativeTools` (both commands) or the conversation's Settings toggle turns them off.
|
|
122
138
|
- `--title` names the session in Agent Sessions. `agent start` is `agent chat --detach` for a new thread; use `chat --type … --timeout …` instead when you want to wait for a short run to finish.
|
|
139
|
+
- `agent show <threadId> --wait` waits for a new thread's first turn (`agent start`, `agent serve --message` without `--thread`), or for a run already in progress, to finish or stop for input, then prints it, using the same completion check as `agent chat`. A turn queued on a thread whose last turn finished still reads as done until it starts, so send follow-ups with `agent chat --thread`, which waits for its own turn. `--timeout` sets the wait in seconds (default 600). It exits non-zero when the wait times out or the run ended in an error.
|
|
123
140
|
|
|
124
141
|
## Notes
|
|
125
142
|
|
|
@@ -145,15 +162,16 @@ Codex uses `CODEX_PATH` when set, then `codex` on PATH, then the managed runtime
|
|
|
145
162
|
```bash
|
|
146
163
|
specset agent serve --host <deployment> --org <slug> & # --account claude2,codex limits it to those accounts
|
|
147
164
|
specset agent show <thread link> # the transcript so far
|
|
165
|
+
specset agent show <thread link> --wait # wait for the run to finish or stop for input
|
|
148
166
|
```
|
|
149
167
|
|
|
150
168
|
What the harness spends is recorded on the thread as unbilled local usage (per model, priced for information only) and kept out of Specset's cost and credit figures.
|
|
151
169
|
|
|
152
170
|
### Native tools and the run workspace
|
|
153
171
|
|
|
154
|
-
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting
|
|
172
|
+
Native tools are on for a thread that starts running locally, unless the thread already chose (an earlier on or off stands): the harness can then run shell commands and read, search or edit files in the run workspace. Turn them off with **Native tools** in the conversation's Settings or `--noNativeTools`; with them off, the harness only calls Specset functions, which the server authorizes and executes (approvals stay in the web app). Each action is mirrored into the conversation as a local action card, and the thread's **Permissions** setting picks the tools: Read only (reads), Accept edits (and edits), Auto or Full access (and the shell; Full asks nothing). In every mode the commands and file tools are sealed in the run's workspace: commands read nothing of the machine but the system and its developer tools (Python, git, Homebrew), write only in the workspace and a private temp directory, reach no network, and never see a credential, so your home, `~/.ssh`, the keychains, every harness's home, `~/.config`, the runner's other directories and the shared temp directories stay closed. Claude Code runs Bash in its command sandbox, which also refuses writes to `.git/config` and `.git/hooks` (git works in an existing repository, but a new one can't be created there), and a hook holds its file tools to the workspace. That hook checks a path before Claude Code opens it, so in Auto and Full a command left running in the background could race it by swapping in a symlink; the commands themselves stay sealed. Codex runs under a permissions profile the runner builds and asks for no approval, so an escalation is declined. The seal needs macOS and, for Codex, a binary the runner checked enforces the profile; elsewhere a step runs with Specset tools only. Native sub-agents stay off; Specset's own sub-agent and fan-out tools run through the same machine. Native tools apply where the harness can run them, so turning them on or off never moves a running thread to another harness. Antigravity uses Specset tools only unless it is on macOS, its `agy --version` is a checked release (1.4.2), and the owner has set `runner.antigravityNativeTools.escapeTestedVersion: "1.4.2"` in `~/.config/specset/config.yml` after the sandbox escape tests pass. A different or unreadable version turns its native tools off again. Its commands use agy's sandbox. Recursive native searches (`grep_search`, `find_by_name`) are disabled; use sandboxed command searches in Auto/Full modes. Its file tools use the same real-path check with the same concurrent-symlink race limitation as Claude Code. It launches from a private read-only project so configuration left in the shared workspace cannot run as agy hooks or MCP servers; writes to `.agents/`, `AGENTS.md` and `GEMINI.md` are also refused by file tools. Clipboard and other macOS services remain reachable from the command sandbox. The Settings menu still labels Antigravity tiers as running without native tools; this opt-in capability is checked by the runner, and helper document reads on Antigravity still run without code.
|
|
155
173
|
|
|
156
|
-
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty
|
|
174
|
+
Each run (a root thread and its children) works in one private directory under `--directory` (`~/.specset/personal-runs/<runner>/runs/<thread>/` by default) with `specs/`, `drawings/`, `docs/`, `inputs/` and `out/`. It starts empty and has no network, so the harness cannot download into it: project material comes through Specset tools, and the model writes what it needs from their results into the workspace. On Antigravity, attachments and file results are saved separately in private read-only input storage at the absolute paths supplied to the model; copy them into the workspace before editing. Findings cite the Specset link, never the local path.
|
|
157
175
|
|
|
158
176
|
With Native tools on and Permissions Auto or Full, the vision tier's document and drawing reads (`examineDrawingSheets`, `examineDocument`, `examineFile` and the like) may also run code to look closer at their attachments: crop a dense detail from the full-resolution file and view it enlarged. Such a read runs in its own scratch directory holding only its attachments (`inputs/attachment-N.png|pdf`), never the run workspace. Its commands are sandboxed: no network, no credentials in their environment, and nothing readable but the system, developer tools, Homebrew (for Python and Pillow) and that directory. Your own temp directory, where other applications keep files, stays closed except, under Claude Code, for the scratch files macOS's `sips` saves there; the shared `/tmp` stays closed. Claude Code's command sandbox denies reading by default, a hook holds its Read tool to the directory, and zsh skips your startup files; Codex runs under a permissions profile that adds only Homebrew and its own binary, with the core environment, so it crops with Pillow and `sips` fails; the runner first checks once that the binary enforces the profile (Codex before 0.157.1 ignores its deny entries), and otherwise runs these reads without code. Code execution needs macOS: on Linux, Claude Code's bubblewrap sandbox leaves the credentials it removes readable to commands through its own process, so these reads run without code there. Its actions are printed in the `agent serve` terminal, because a helper read's actions never reach the conversation. These reads were evaluated on the cloud models without code execution, so treat code-execution results as unevaluated.
|
|
159
177
|
|
|
@@ -167,13 +185,13 @@ Connect to an organization with `specset agent serve --host <host> --org <slug|i
|
|
|
167
185
|
|
|
168
186
|
`agent serve` uses server-owned orchestration: Temporal executes the agent and its tools, while model requests run through the connected machine. This includes generic drawing-coordination assessments, image inputs and structured output. The runner keeps a native session alive across each complete parallel tool batch and sends back the server's authorized results. All Specset approvals remain in the web app; native tools follow the thread's **Native tools** setting (see above) and native delegation stays off. Independent model requests use independent sessions. Claude uses `persistSession: false`; Codex uses `ephemeral: true`.
|
|
169
187
|
|
|
170
|
-
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for `serve`). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
188
|
+
For an unattended kickoff, `--autoApprovePlan` skips the plan sign-off and `--autoApprove` approves Specset tools for that message (default off for a thread `serve` creates; with `--thread`, omit it to keep the thread's setting). `--isolateIssues` ignores pre-existing project issues when planning a bounded test. The command prints the created or assigned thread link. File attachments and file tool results preserve bytes, MIME type and optional filename through the same transport. Claude receives raw PDFs; Codex receives server-rendered PDF page images, capped at 20 pages per request across all PDFs. Both support image inputs and UTF-8 text attachments. Other binary formats fail explicitly until a compatible reader or conversion is available.
|
|
171
189
|
|
|
172
|
-
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve` or `agent
|
|
190
|
+
Pin both `--host <deployment>` and `--org <slug>` when using `agent chat`, `agent start`, `agent show`, `agent inspect`, `agent approve`, `agent stop` or `agent workers`; these overrides do not change the CLI’s saved selection.
|
|
173
191
|
|
|
174
|
-
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
192
|
+
Every model call carries its routing tier (conversation, orchestrator, planner, analyst, worker, lite, balanced, query, vision), and each tier names a model that any account whose harness offers it serves. The defaults match the app; `specset harness list` prints each tier's current model and the class it runs as. Antigravity is opt-in: it serves those tiers only after you add an account for it (`specset harness add agy --harness antigravity`); until then they run their model's class on Claude Code or Codex, or in the cloud with `harness fallback`. A model no account offers runs as its class (frontier, standard, fast) on a harness the machine has. Change a tier with `specset harness tier <tier> <model[:effort]>`, a class's model with `specset harness model <harness> <class> <model> [--effort e]`, or one run's with `agent serve --tier conversation=opus`. The conversation's Settings pins a class or one of the reported models per harness for that thread's own steps, plus a reasoning effort; children and helper calls keep the tiers. When the preferred accounts for a model are at their limits, `specset harness failover sameModel|any|off` decides where its calls go (default sameModel: other accounts with the same model; any: also the same class elsewhere); a call that already streamed fails instead and the next one goes elsewhere. A provider rate limit (HTTP 429) is waited out rather than failing the run: the server retries the call once the account's short wait passes (on another account if one has room), unless it already ran a native command or edit, which a retry could repeat; only every account being at its usage limit stops the run. A call whose input a harness refuses for its size (Codex caps a turn's input at 1,048,576 characters) is never retried there: it moves to another harness the failover setting allows, else to its tier's cloud model when the tier falls back to the cloud, else fails naming the cause. `specset harness fallback <tier|all> cloud` (or `agent serve --fallback analyst=cloud`) sends a tier's calls to Specset's cloud model, billed to the organization, instead of another provider's whenever no account here can serve its model (none offers it, or its accounts are at their limits); `local` is the default. `--concurrency` defaults to 5 (at most 16) and limits active model calls; tool and approval waits release their slot so even 1 can complete nested fan-out. `--maxThreads` defaults to 100 and `--maxRunSeconds` to 3600 per root authorization. `specset harness limits --concurrency 16 --maxThreads 300 --maxRunSeconds 43200` saves them in config.yml for every `agent serve` (a flag still wins for one run; `default` clears one; no flags prints them). Specset’s `--autoApprove` message setting controls tool approval; the thread's Permissions setting bounds native tools when they are on. Ctrl-C revokes the registration and stops queued/running descendants. A restart needs a new assignment. A lost native continuation fails visibly rather than replaying writes or switching to a cloud model.
|
|
175
193
|
|
|
176
|
-
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment
|
|
194
|
+
Harness tool callbacks can only wait for results supplied by the server. Each job receives private harness configuration and a minimal environment, and no CLI login: a native session's commands reach no network, so project material comes through Specset tools. Claude shares the selected native credential store and its refresh lock. Codex uses an authentication-only native process to refresh the selected login, then supplies temporary access tokens to isolated inference sessions. Neither copies rotating refresh credentials into job homes. Settings, hooks, plugins and global MCP servers are excluded from inference sessions. Run `claude auth login` first if the harness reports authentication is required. The harness itself still runs as the local OS user, outside the seal its commands run in. Use a dedicated account or isolated host for untrusted project content; do not share this personal runner across users.
|
|
177
195
|
|
|
178
196
|
Use `specset agent runners list --json` to inspect your registrations.
|
|
179
197
|
Use `specset agent runners revoke <runner-id>` to stop and revoke one remotely, including an offline runner after a crash.
|
|
@@ -12,6 +12,8 @@ Run every operation with `specset api`. All ids are plain UUIDs. Inline enums, n
|
|
|
12
12
|
|
|
13
13
|
**Locations** are the spatial scaffold (site → building → level → room), nesting through `parentId`. **Systems** are disciplines and equipment groups, also nested. **Products** are specified make/models. **Assets** are installed instances: each asset has a location, may reference a product and responsible company, and can join multiple systems. **Warranties** attach to assets.
|
|
14
14
|
|
|
15
|
+
**Companies** belong to the organization's directory and are shared by its projects. A company works on a project through a **ProjectCompany** row, which holds that project's `role`, `notes`, `archived` state and closeout documentation. Assigning a directory company to a project record (an asset's responsible company, a part's vendor, a submittal's or RFI's responsible contractor) adds it to the project.
|
|
16
|
+
|
|
15
17
|
Maintenance has three layers:
|
|
16
18
|
|
|
17
19
|
- **Procedure** — reusable checklist content: ordered typed fields, descriptions, and source references.
|
|
@@ -53,6 +55,7 @@ Notable differences:
|
|
|
53
55
|
|
|
54
56
|
- Asset creation requires `projectId` and `locationId`. `paginatedAssets` accepts `search` and filters such as `statuses`, `locationIds`, `productIds`, `systemIds`, `responsibleCompanyIds`, `parentIds`, and `archived`.
|
|
55
57
|
- Warranty is asset-scoped; use `warrantiesByAsset(assetId)` to browse one asset's warranties.
|
|
58
|
+
- Company is org-wide: `paginatedCompanies(orgId: ...)` lists the directory and `searchCompanies(orgId, search)` finds a company by name. `paginatedProjectCompanies(projectId)` lists a project's companies with their `role`; `addCompaniesToProject(projectId, companyIds)`, `removeCompaniesFromProject(projectId, companyIds)` (refused while a project record still names the company) and `updateProjectCompany(input: { id, role, notes, archived })` manage the participation. `createCompany(input: { projectId, ... })` creates the directory company and puts it on that project.
|
|
56
59
|
- Location, Product, Company, System, and Asset updates support reversible `archived` state. Prefer archiving to deletion.
|
|
57
60
|
- Batch-delete availability varies. Introspect the mutation root rather than guessing an operation name.
|
|
58
61
|
|
|
@@ -175,7 +178,7 @@ specset api --query 'mutation { setProductDocumentRoles(productId: "<product-id>
|
|
|
175
178
|
|
|
176
179
|
Existing project Documents work too — a submittal attachment's `document { id }` (from `paginatedSubmittals … attachments { document { id title } }`) can be linked directly as `SubmittalPackage`/`Datasheet`; the only check is that the Document belongs to the same project. To bind a slice of a large file, `extractPagesFromFileResource(id: <fileResourceId>, pages: [3, 4, 5])` returns a new CloudFile to feed into `createDocumentsFromCloudFiles`. Only PDF files merge into the O&M package export; images and other types are listed as references.
|
|
177
180
|
|
|
178
|
-
Asset, Location, Product, Company, and System also have markdown documentation blocks. `update<Entity>Documentation(id, markdown)` replaces the block; `mark<Entity>DocumentationComplete(id)` attests that it is current. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
181
|
+
Asset, Location, Product, Company, and System also have markdown documentation blocks. `update<Entity>Documentation(id, markdown)` replaces the block; `mark<Entity>DocumentationComplete(id)` attests that it is current. A company's block is per project: use `updateProjectCompanyDocumentation` / `markProjectCompanyDocumentationComplete` with the ProjectCompany id. Any later create/update/link on the entity (a new document pivot, a warranty document, a field edit) marks the documentation stale again, so when seeding a graph write documentation **last**. Calling `mark<Entity>DocumentationComplete` with an empty block sets the state to `NotApplicable`, which the package readiness report accepts — use it for entities that don't warrant prose rather than writing filler.
|
|
179
182
|
|
|
180
183
|
### Writing documentation that reads well in the O&M package export
|
|
181
184
|
|
|
@@ -324,7 +327,7 @@ For duplicate products P1 (loser) and P2 (survivor):
|
|
|
324
327
|
3. Re-run the filtered query and confirm zero remaining references.
|
|
325
328
|
4. Archive P1 with `updateProduct(input: { id: P1, archived: true })`.
|
|
326
329
|
|
|
327
|
-
Use the same impact-first pattern for duplicate companies,
|
|
330
|
+
Use the same impact-first pattern for duplicate locations and systems. Duplicate companies merge in one step: `mergeCompanies(targetId, sourceIds)` moves their projects, documents, record assignments, people and Procore links to the target, fills the target's blank profile fields from theirs (in `sourceIds` order), and archives the duplicates; it is not reversible, so confirm the pair first. `companyDuplicates(orgId)` lists likely pairs (names that differ only in case, punctuation or a legal form), and `companies(ids)` loads them side by side.
|
|
328
331
|
|
|
329
332
|
## Safety
|
|
330
333
|
|
|
@@ -18,7 +18,7 @@ Two common flows have purpose-built commands; prefer them to hand-written mutati
|
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
specset submittals create --project <projectId> --file ./package.pdf --json # uploads the file and creates the submittal from it; --title, repeatable --specSection <id>
|
|
21
|
-
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
21
|
+
specset submittals review --submittal <submittalId|link> --wait --json # runs a compliance review and prints its issues; exits non-zero when it times out or the review fails; repeatable --specSection, --prompt, --timeout (default 3600)
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
## Reading
|
|
@@ -65,15 +65,15 @@ The retrieval tools return **extracted text**, which is authoritative for schedu
|
|
|
65
65
|
| Entity | Command | Also accepts | Formats / options |
|
|
66
66
|
| -------------- | ----------------------------------------------------------------------- | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
|
67
67
|
| Drawing sheets | `specset drawings download 'https://app.specbook.ai/go/drawing/<uuid>'` | sheet number + `--project` | `--format image` (rendered PNG, default), `pdf` (single sheet), `file` (whole set) |
|
|
68
|
-
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | also covers RFI attachments and closeout documents
|
|
68
|
+
| Documents | `specset docs download 'https://app.specbook.ai/go/doc/<uuid>'` | | `--original` (uploaded file, not the converted PDF); also covers RFI attachments and closeout documents |
|
|
69
69
|
| Spec sections | `specset specs download 'https://app.specbook.ai/go/spec/<uuid>'` | section number (e.g. `'23 31 19'`) + `--project` | per-section PDF slice (default), `--asset book` (whole source spec file) |
|
|
70
70
|
| Submittals | `specset submittals download 'https://app.specbook.ai/go/sub/<uuid>'` | | compiled package (default when present); `--list` shows the asset inventory; `--asset attachment --index 2`, `--asset attachment --all` |
|
|
71
71
|
| RFIs | `specset rfis download 'https://app.specbook.ai/go/rfi/<uuid>'` | | question + response attachments; `--list`, `--all` |
|
|
72
|
-
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link
|
|
72
|
+
| File resources | `specset files download 'https://app.specbook.ai/go/file/<uuid>'` | | escape hatch for a returned file source link; `--original` (uploaded file, not the converted PDF) |
|
|
73
73
|
|
|
74
74
|
All commands support `--json` (structured `{files: [{path, bytes, ...}]}` output) and `--out` (file path, or directory for multi-file downloads). A citation passed to the wrong command errors with the right one to use.
|
|
75
75
|
|
|
76
|
-
`drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
76
|
+
`docs download`, `drawings download`, `files download` and `specs download` also accept `--org <slug|id>` to pin the source organization when parallel sessions might change the active organization, for example `specset drawings download P2.120 --project <id> --org <slug> --host <host> --format pdf` or `specset specs download '08 71 00' --project <id> --org <slug> --host <host> --out hardware.pdf`.
|
|
77
77
|
|
|
78
78
|
To inspect an existing detail crop, add `--node <shortId|UUID>`, for example `specset drawings download <sheet-ref> --node d6 --format image --out detail.png`. Use `--format pdf` for the saved crop PDF. This downloads the current revision's stored crop without regenerating it; missing nodes or crops return an error. Node crops do not support `--format file`. JSON output adds the resolved `nodeId` to the normal sheet download metadata.
|
|
79
79
|
|