@impulselab/hepha 0.2.94 → 0.2.96
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 +45 -0
- package/package.json +16 -2
- package/skills/cli/SKILL.md +338 -0
package/README.md
CHANGED
|
@@ -45,3 +45,48 @@ CLI at another Hephaistos deployment.
|
|
|
45
45
|
- `1`: failed, killed, or abstained
|
|
46
46
|
- `2`: invalid CLI usage
|
|
47
47
|
- `3`: waiting for a human question or approval
|
|
48
|
+
|
|
49
|
+
## Agent skill
|
|
50
|
+
|
|
51
|
+
The package ships `skills/cli/SKILL.md`, an [Agent
|
|
52
|
+
Skill](https://agentskills.io) that teaches a coding agent what the platform
|
|
53
|
+
does and how to drive it — task types, the question cycle, the four guardrails,
|
|
54
|
+
then the commands, the exit codes and the mistakes that look right. Install the
|
|
55
|
+
package and the knowledge travels with it; `npm update` updates it.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx @tanstack/intent@latest install # writes discovery guidance once
|
|
59
|
+
npx @tanstack/intent@latest list --global # the CLI is usually installed globally
|
|
60
|
+
npx @tanstack/intent@latest load @impulselab/hepha#cli
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Nothing runs on install: the skill is a file in the tarball and the tooling
|
|
64
|
+
reads it out of `node_modules`, never executing package code. Discovery is
|
|
65
|
+
opt-in per source, in the consuming project's `package.json`:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"intent": {
|
|
70
|
+
"skills": ["@impulselab/hepha"],
|
|
71
|
+
"exclude": ["@impulselab/hepha#experimental-*"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`intent.skills` is the package-level allowlist (`"*"` allows everything, `[]`
|
|
77
|
+
nothing), `intent.exclude` the finer control down to `package#skill`. That list
|
|
78
|
+
is the boundary — `intent hooks install` only nudges an editor to surface skill
|
|
79
|
+
loading, and must never be what decides a skill is safe to load.
|
|
80
|
+
|
|
81
|
+
### Maintaining it
|
|
82
|
+
|
|
83
|
+
`sources:` in the skill's frontmatter names the files it is distilled from.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pnpm --filter @impulselab/hepha exec intent validate skills # blocking in CI
|
|
87
|
+
pnpm --filter @impulselab/hepha run skills:stale # advisory
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`.github/workflows/skills.yml` validates the skill on every PR that touches it,
|
|
91
|
+
and reports — without failing the build — when a PR edits a declared source
|
|
92
|
+
without touching the skill.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@impulselab/hepha",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.96",
|
|
4
4
|
"description": "Command-line interface for the Hepha platform.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -12,6 +12,16 @@
|
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/impulse-studio/hephaistos/issues"
|
|
14
14
|
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"hephaistos",
|
|
17
|
+
"agent-skills",
|
|
18
|
+
"tanstack-intent"
|
|
19
|
+
],
|
|
20
|
+
"intent": {
|
|
21
|
+
"version": 1,
|
|
22
|
+
"repo": "impulse-studio/hephaistos",
|
|
23
|
+
"docs": "https://hephaistos.impulselab.ai/api/v1/docs"
|
|
24
|
+
},
|
|
15
25
|
"publishConfig": {
|
|
16
26
|
"access": "public",
|
|
17
27
|
"registry": "https://registry.npmjs.org"
|
|
@@ -20,13 +30,16 @@
|
|
|
20
30
|
"hepha": "dist/index.js"
|
|
21
31
|
},
|
|
22
32
|
"files": [
|
|
23
|
-
"dist"
|
|
33
|
+
"dist",
|
|
34
|
+
"skills"
|
|
24
35
|
],
|
|
25
36
|
"scripts": {
|
|
26
37
|
"build": "tsup",
|
|
27
38
|
"dev": "tsx src/index.ts",
|
|
28
39
|
"types:check": "pnpm --filter @repo/ui run build && tsc --noEmit",
|
|
29
40
|
"test": "find src -name '*.test.ts' -type f -exec tsx --test {} +",
|
|
41
|
+
"skills:validate": "intent validate skills",
|
|
42
|
+
"skills:stale": "intent stale .",
|
|
30
43
|
"prepublishOnly": "npm run types:check",
|
|
31
44
|
"prepack": "tsup"
|
|
32
45
|
},
|
|
@@ -38,6 +51,7 @@
|
|
|
38
51
|
"@repo/database": "workspace:*",
|
|
39
52
|
"@repo/server": "workspace:*",
|
|
40
53
|
"@repo/typescript-config": "workspace:*",
|
|
54
|
+
"@tanstack/intent": "0.3.6",
|
|
41
55
|
"@types/node": "^22.15.3",
|
|
42
56
|
"tsup": "^8.4.0",
|
|
43
57
|
"tsx": "^4.19.4",
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: cli
|
|
3
|
+
description: >
|
|
4
|
+
Drive the Hephaistos code factory from a terminal or a script with the
|
|
5
|
+
`hepha` CLI: create a session from a prompt, follow its transcript, answer
|
|
6
|
+
its question rounds, read the delivery, and escalate guardrail approvals to
|
|
7
|
+
a human. Load when the task mentions hepha, Hephaistos, HEPHA_API_KEY,
|
|
8
|
+
`hepha run`, exit code 3, waiting_human, awaiting_approval, question
|
|
9
|
+
batches, or the `/api/v1` public API.
|
|
10
|
+
metadata:
|
|
11
|
+
type: core
|
|
12
|
+
library: "@impulselab/hepha"
|
|
13
|
+
library_version: "0.2"
|
|
14
|
+
sources:
|
|
15
|
+
- "impulse-studio/hephaistos:apps/cli/README.md"
|
|
16
|
+
- "impulse-studio/hephaistos:apps/cli/src/commands/*.ts"
|
|
17
|
+
- "impulse-studio/hephaistos:apps/cli/src/lib/follow.ts"
|
|
18
|
+
- "impulse-studio/hephaistos:apps/cli/src/lib/session-outcome.ts"
|
|
19
|
+
- "impulse-studio/hephaistos:packages/database/constants.ts"
|
|
20
|
+
- "impulse-studio/hephaistos:packages/server/src/routers/v1/*/router.ts"
|
|
21
|
+
- "impulse-studio/hephaistos:packages/server/src/routers/v1/schemas.ts"
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# Hephaistos from the command line
|
|
25
|
+
|
|
26
|
+
## What the platform does
|
|
27
|
+
|
|
28
|
+
Hephaistos is a code factory: a demand goes in, a **session** carries it to a
|
|
29
|
+
delivered result. One session is one accountable run — it owns an isolated
|
|
30
|
+
cloud sandbox, a transcript, and a lifecycle you can drive from this CLI.
|
|
31
|
+
|
|
32
|
+
| Task type | What the session produces |
|
|
33
|
+
| ------------- | ----------------------------------------------------------- |
|
|
34
|
+
| `implement` | A change on an existing repo, delivered as a pull request |
|
|
35
|
+
| `new-project` | A new repo scaffolded, provisioned, planned and implemented |
|
|
36
|
+
| `question` | An answer — no git setup, repos cloned read-only to explore |
|
|
37
|
+
| `draft` | Written output (email, spec, recap) — no code delivery |
|
|
38
|
+
|
|
39
|
+
Three platform behaviours decide how a caller must drive a session:
|
|
40
|
+
|
|
41
|
+
- **The question cycle is infinite.** An agent that is not confident asks
|
|
42
|
+
instead of guessing. The session moves to `waiting_human` and stays there
|
|
43
|
+
forever until a round is answered — it costs nothing and never times out.
|
|
44
|
+
Answering is a normal scope, so a script or an agent can do it.
|
|
45
|
+
- **Four guardrails are human-only**: merge a pull request, touch production,
|
|
46
|
+
reply to an external client, push to a main branch. The session moves to
|
|
47
|
+
`awaiting_approval` and an API key can read the request but never decide it.
|
|
48
|
+
- **Delivery is proven, not claimed.** A finished code session carries a PR
|
|
49
|
+
URL, a verification report (tests + recorded proof) and its CI status —
|
|
50
|
+
read them to judge the result.
|
|
51
|
+
|
|
52
|
+
Sessions run in parallel on the same repo without conflict: isolation is by
|
|
53
|
+
branch, and collisions are resolved at PR time.
|
|
54
|
+
|
|
55
|
+
## Setup
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npm install --global @impulselab/hepha
|
|
59
|
+
|
|
60
|
+
# Agents: a key minted by a human with `hepha keys create`.
|
|
61
|
+
export HEPHA_API_KEY=hepha_xxxxxxxxxxxxxxxxxxxxxxxx
|
|
62
|
+
hepha whoami --json
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
`whoami --json` reports the scopes actually in play — read it first and fail
|
|
66
|
+
early instead of halfway through a task.
|
|
67
|
+
|
|
68
|
+
| Variable | Effect |
|
|
69
|
+
| ------------------ | ------------------------------------------------------------------- |
|
|
70
|
+
| `HEPHA_API_KEY` | Act as an agent. **Takes precedence** over a stored `hepha login`. |
|
|
71
|
+
| `HEPHA_TOKEN` | A device-flow token, instead of `~/.hepha/config.json`. |
|
|
72
|
+
| `HEPHA_URL` | Platform base URL (a dev box points it at `http://localhost:3000`). |
|
|
73
|
+
| `HEPHA_TIMEOUT_MS` | Per-request timeout, default `30000`. |
|
|
74
|
+
|
|
75
|
+
Humans sign in with `hepha login` (device authorization, approved in a
|
|
76
|
+
browser). It is the one interactive command and the one that does not honour
|
|
77
|
+
`--json`. An agent should never run it.
|
|
78
|
+
|
|
79
|
+
Scopes are `resource:action`: `sessions:read`, `sessions:write`,
|
|
80
|
+
`questions:read`, `questions:answer`, `approvals:read`, `catalog:read`,
|
|
81
|
+
`pull_requests:read`. A key created without `--scope` is read-only across
|
|
82
|
+
every resource, and scopes can never be widened — only replaced by a new key.
|
|
83
|
+
|
|
84
|
+
## Core patterns
|
|
85
|
+
|
|
86
|
+
### Start a session and follow it to its outcome
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
hepha repos --json # repoFullName must exist in the catalog
|
|
90
|
+
hepha run "Add rate limiting to the public API" \
|
|
91
|
+
--repo impulse-studio/hephaistos \
|
|
92
|
+
--task-type implement \
|
|
93
|
+
--follow
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`run --follow` creates the session, streams the transcript, and returns the
|
|
97
|
+
moment the run finishes or needs a person. The exit code carries the outcome
|
|
98
|
+
so nothing has to be parsed:
|
|
99
|
+
|
|
100
|
+
| Code | Meaning |
|
|
101
|
+
| ---- | -------------------------------------------------------- |
|
|
102
|
+
| `0` | Session completed |
|
|
103
|
+
| `1` | Failed, killed or abstained (message on stderr) |
|
|
104
|
+
| `2` | Usage error (unknown flag, missing argument, bad value) |
|
|
105
|
+
| `3` | **Blocked on a human** — a question round or a guardrail |
|
|
106
|
+
|
|
107
|
+
### Branch a script on the outcome
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
result=$(hepha run "$PROMPT" --repo "$REPO" --follow --json)
|
|
111
|
+
status=$?
|
|
112
|
+
session=$(jq -r '.id // .result.id // empty' <<<"$result")
|
|
113
|
+
|
|
114
|
+
case "$status" in
|
|
115
|
+
0) hepha sessions pr "$session" --json ;;
|
|
116
|
+
3) hepha questions list --session "$session" --json ;;
|
|
117
|
+
*) exit "$status" ;;
|
|
118
|
+
esac
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Under `--json` the session is the document on success and rides in `.result`
|
|
122
|
+
on a non-zero exit, so `.id // .result.id` names the session either way — the
|
|
123
|
+
id is never printed anywhere else.
|
|
124
|
+
|
|
125
|
+
Exit `3` is not a failure: it is the platform asking for input. Answer the
|
|
126
|
+
round (or escalate the guardrail), then keep following.
|
|
127
|
+
|
|
128
|
+
### Answer a question round
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
hepha questions list --json # batches waiting on you
|
|
132
|
+
hepha questions show <batchId> # ids, options, recommendation
|
|
133
|
+
hepha questions answer <batchId> \
|
|
134
|
+
--option q_01=2 \
|
|
135
|
+
--text q_02="Use Postgres, not SQLite"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`--option <questionId>=<optionId>[,<optionId>]` selects; `--text
|
|
139
|
+
<questionId>=…` adds free text; both may target the same question. For an
|
|
140
|
+
agent, one flag carries the whole round:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
hepha questions answer <batchId> \
|
|
144
|
+
--answers '[{"questionId":"q_01","selectedOptionIds":["2"]},{"questionId":"q_02","freeText":"Use Postgres"}]'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Then resume following: `hepha sessions logs <id> --follow`.
|
|
148
|
+
|
|
149
|
+
### Poll a transcript incrementally
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
hepha sessions logs <id> --after 128 --limit 200 --json
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
`--after <sequence>` returns only what came after that event. The page carries
|
|
156
|
+
`nextCursor` — pass it back as `--after` — and `hasMore`, which is `false`
|
|
157
|
+
when you are caught up. `--follow` blocks until the session finishes or parks.
|
|
158
|
+
|
|
159
|
+
### Collect the delivery
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
hepha sessions get <id> --json # status, prUrl, playbook
|
|
163
|
+
hepha sessions verify <id> --json # test report and recorded proof
|
|
164
|
+
hepha sessions pr <id> --json # CI status of the delivered PR
|
|
165
|
+
hepha sessions checks <id> # re-run the repo's checks
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Hand a guardrail to a human
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
hepha approvals list --json # readable with approvals:read
|
|
172
|
+
hepha approvals show <id> --json
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Approving or rejecting requires a human session (`hepha login`), so an agent
|
|
176
|
+
stops here and reports the pending approval to a person.
|
|
177
|
+
|
|
178
|
+
## Command reference
|
|
179
|
+
|
|
180
|
+
| Command | Notes |
|
|
181
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------ |
|
|
182
|
+
| `hepha run "<prompt>"` | `--repo --branch --task-type --model --harness --skill --title --follow` |
|
|
183
|
+
| `hepha sessions list` | `--status active\|waiting\|done --repo --search --page --limit --all` |
|
|
184
|
+
| `hepha sessions get\|logs\|send\|pause\|resume\|kill\|archive <id>` | `logs --after <sequence> --follow`, `kill --reason "…"` |
|
|
185
|
+
| `hepha sessions checks\|pr\|verify <id>` | Re-run checks · CI status · proof artifacts |
|
|
186
|
+
| `hepha questions list\|show\|answer\|cancel` | `list --session <id>` |
|
|
187
|
+
| `hepha approvals list\|show\|approve\|reject` | `approve`/`reject` are human-session only |
|
|
188
|
+
| `hepha repos \| branches <owner/repo> \| models \| skills` | The catalog to read before creating a session |
|
|
189
|
+
| `hepha keys list\|create\|revoke` | Human-session only |
|
|
190
|
+
| `hepha login \| logout \| whoami` | `login` is interactive |
|
|
191
|
+
|
|
192
|
+
Global flags: `--json` on every command that returns data, `--quiet` to drop
|
|
193
|
+
the streamed transcript while following, `--version`, `--help`.
|
|
194
|
+
|
|
195
|
+
## Common Mistakes
|
|
196
|
+
|
|
197
|
+
### [CRITICAL] Retrying `hepha run` after exit 3
|
|
198
|
+
|
|
199
|
+
Wrong:
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
hepha run "$PROMPT" --repo "$REPO" --follow || hepha run "$PROMPT" --repo "$REPO" --follow
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Correct:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
hepha run "$PROMPT" --repo "$REPO" --follow
|
|
209
|
+
status=$?
|
|
210
|
+
|
|
211
|
+
case "$status" in
|
|
212
|
+
0) ;; # delivered
|
|
213
|
+
3) hepha questions list --json ;; # answer it, then follow again
|
|
214
|
+
*) exit "$status" ;; # 1 failed · 2 usage — never remapped
|
|
215
|
+
esac
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Exit 3 means the existing session is parked on a question or a guardrail.
|
|
219
|
+
Re-running creates a second session on the same repo while the first one waits
|
|
220
|
+
forever.
|
|
221
|
+
|
|
222
|
+
Source: apps/cli/src/lib/session-outcome.ts
|
|
223
|
+
|
|
224
|
+
### [HIGH] Approving a guardrail with an API key
|
|
225
|
+
|
|
226
|
+
Wrong:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
export HEPHA_API_KEY=hepha_xxx
|
|
230
|
+
hepha approvals approve "$GUARDRAIL_ID"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Correct:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
hepha approvals show "$GUARDRAIL_ID" --json # read it, then tell a human
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`approvals:decide` is not a scope that exists — the route answers `403` to any
|
|
240
|
+
key. `HEPHA_API_KEY` also takes precedence over a stored login, so a human
|
|
241
|
+
whose shell exports it gets `403` on every human-only command until it is
|
|
242
|
+
unset.
|
|
243
|
+
|
|
244
|
+
Source: packages/server/src/routers/v1/approvals/router.ts (humanSessionMiddleware)
|
|
245
|
+
|
|
246
|
+
### [HIGH] Parsing the human output instead of `--json`
|
|
247
|
+
|
|
248
|
+
Wrong:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
hepha sessions get "$ID" | grep -i "pr:" | awk '{print $2}'
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Correct:
|
|
255
|
+
|
|
256
|
+
```bash
|
|
257
|
+
hepha sessions get "$ID" --json | jq -r '.prUrl // empty'
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The aligned text is padded and coloured for a terminal. Under `--json` stdout
|
|
261
|
+
is one parseable document, including failures — `{"ok":false,"error":{…}}`,
|
|
262
|
+
with the exit code unchanged.
|
|
263
|
+
|
|
264
|
+
Source: apps/cli/src/lib/output.ts
|
|
265
|
+
|
|
266
|
+
### [HIGH] Re-reading the whole transcript on every poll
|
|
267
|
+
|
|
268
|
+
Wrong:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
while :; do hepha sessions logs "$ID" --json; sleep 5; done
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Correct:
|
|
275
|
+
|
|
276
|
+
```bash
|
|
277
|
+
after=0
|
|
278
|
+
while :; do
|
|
279
|
+
page=$(hepha sessions logs "$ID" --after "$after" --limit 200 --json)
|
|
280
|
+
after=$(jq -r "[$after, .nextCursor // 0, (.items[]?.sequence)] | max" <<<"$page")
|
|
281
|
+
[ "$(jq -r '.hasMore' <<<"$page")" = "true" ] || sleep 3
|
|
282
|
+
done
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
Without `--after`, every poll re-downloads the transcript from the start and
|
|
286
|
+
the consumer re-processes events it has already seen. Advance on the highest of
|
|
287
|
+
both signals: `nextCursor` is `null` on the last page even when that page
|
|
288
|
+
carried events, and an event the API cannot render is dropped from `items`
|
|
289
|
+
while still consuming a sequence — following either one alone stalls the
|
|
290
|
+
cursor or spins on the same page.
|
|
291
|
+
|
|
292
|
+
Source: apps/cli/src/lib/follow.ts · packages/server/src/services/session-events-query.service.ts
|
|
293
|
+
|
|
294
|
+
### [MEDIUM] Sending `--answers` as an object
|
|
295
|
+
|
|
296
|
+
Wrong:
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
hepha questions answer "$BATCH" --answers '{"q_01":"2"}'
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
Correct:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
hepha questions answer "$BATCH" --answers '[{"questionId":"q_01","selectedOptionIds":["2"]}]'
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
`--answers` takes a non-empty JSON **array** of `{questionId,
|
|
309
|
+
selectedOptionIds?, freeText?}`, and an entry carrying neither an option nor
|
|
310
|
+
free text answers nothing — both fail with exit 2 before any request is sent.
|
|
311
|
+
|
|
312
|
+
Source: apps/cli/src/commands/collect-answers.ts
|
|
313
|
+
|
|
314
|
+
### [MEDIUM] Creating a key without scopes, then writing with it
|
|
315
|
+
|
|
316
|
+
Wrong:
|
|
317
|
+
|
|
318
|
+
```bash
|
|
319
|
+
hepha keys create "ci bot"
|
|
320
|
+
export HEPHA_API_KEY=hepha_xxx
|
|
321
|
+
hepha run "…" --repo "$REPO" # 403
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Correct:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
hepha keys create "ci bot" --scope sessions:read --scope sessions:write \
|
|
328
|
+
--scope questions:read --scope questions:answer \
|
|
329
|
+
--scope approvals:read --scope catalog:read
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
A key created without `--scope` is read-only, and passing any scope **replaces**
|
|
333
|
+
that default instead of adding to it — so the list has to carry everything the
|
|
334
|
+
loop touches: write on sessions, both question scopes (a round nobody can answer
|
|
335
|
+
parks forever) and `approvals:read`, without which the agent cannot even read
|
|
336
|
+
the guardrail it is supposed to escalate.
|
|
337
|
+
|
|
338
|
+
Source: packages/database/constants.ts · packages/server/src/routers/api-keys/mutations/create-api-key.ts
|