@spinalfluid/ultrac 0.0.0-stage → 0.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 CHANGED
@@ -1,3 +1,657 @@
1
- # Temporary Holding Version
1
+ # ultrac
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `ultrac` is the terminal-native coding agent for Ultrac - a Claude Code-style CLI that
4
+ understands your repository, proposes and makes tightly scoped edits, runs verification, and keeps
5
+ an auditable session transcript. It is built for Cosmic 1 over Ultrac's private, OpenAI-compatible
6
+ API. Cosmic 1 is live on GCP, with a native 262,144-token context window shared
7
+ between input and output. The hosted API supports streaming and native tool calls.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install -g @spinalfluid/ultrac
13
+ ultrac login
14
+ ultrac doctor
15
+ ultrac
16
+ ```
17
+
18
+ The npm package is public. Model access still requires an approved Ultrac account and `ultrac login`.
19
+ Requires Node.js >= 22.12.0. The command is `ultrac`; the package is `@spinalfluid/ultrac`.
20
+ The unrelated public packages named `ultrac` and `ultracode` are different products.
21
+
22
+ The CLI checks npm at most once every 24 hours and shows a terminal notice when a newer release is
23
+ available. Registry failures stay silent during normal sessions. Check or update explicitly with:
24
+
25
+ ```bash
26
+ ultrac update --check
27
+ ultrac update
28
+ ```
29
+
30
+ The update command installs `@spinalfluid/ultrac@latest` globally through npm. It never sends Ultrac API
31
+ credentials to npm, and it does not update itself in the middle of an active interactive session.
32
+ For local testing before a registry release, build and install the verified tarball:
33
+
34
+ ```bash
35
+ pnpm check
36
+ pnpm verify:install
37
+ npm pack
38
+ npm install -g ./spinalfluid-ultrac-0.1.0.tgz
39
+ ultrac login
40
+ ```
41
+
42
+ The `echo` provider is an offline wiring check and does not call Cosmic 1. Interactive
43
+ sessions and `ultrac run` execute tools on your machine with the permissions you grant.
44
+ Hosted background tasks need a separate worker deployment and are not ready on GCP.
45
+
46
+ ## Sign in
47
+
48
+ ```bash
49
+ ultrac login # opens the browser; sign in on ultrac.io; the CLI is authenticated
50
+ ultrac whoami # show the current sign-in state and where it came from
51
+ ultrac logout # drop local credentials and revoke the minted key
52
+ ```
53
+
54
+ For interactive use, `ultrac login` opens `https://ultrac.io/cli/login` (or the same path on a local
55
+ site). Approve the CLI using your existing website account; the site mints a key and the CLI stores
56
+ it in `~/.ultrac/auth.json` with `0600` permissions. You do not need to create or paste a key.
57
+
58
+ For scripts and CI, do not run the browser sign-in flow. Create a dedicated API key in the account's
59
+ `/keys` page, store it in your CI secret manager, and expose it to the command as `ULTRAC_API_KEY`.
60
+ The environment key takes precedence over a stored browser login. `ultrac login --manual` prompts
61
+ for an existing key, and `ultrac login --key <key>` verifies and stores one; use these only when you
62
+ intentionally need a local saved credential, not as the default CI setup. Avoid putting secrets in
63
+ shell history, command arguments, project config, or source control. An approved Ultrac account is required for model access.
64
+
65
+ The minted key is checked with `GET <base>/models` before it is saved: a 401/403 is refused and
66
+ nothing is written; if the API cannot be reached the key is saved anyway with a warning.
67
+
68
+ ### Cosmic 1, in a repo
69
+
70
+ Start a live model session in your repository:
71
+
72
+ ```bash
73
+ ultrac login
74
+ cd your-repo
75
+ ultrac
76
+ ```
77
+
78
+ Default and `coding` modes advertise file edits (`write_file`, `replace_in_file`, `multi_edit`,
79
+ `apply_patch`) and `run_shell`. Interactive sessions prompt before a write or a shell. For `run_shell`,
80
+ the prompt shows the full command, resolved working directory, timeout, and a warning that it runs as
81
+ your user with access to files and network available to your account. `once` approves only that
82
+ command; `always` grants shell access to all shell-using tools for the rest of the session. Unattended
83
+ `ultrac run` needs `--allow-write` and `--allow-shell` (or a trusted
84
+ `.ultrac/config.json`). `plan` enforces a read-only tool set; `review` also disables writes, while
85
+ its diagnostics tool still requires explicit shell permission. `ultrac task` queues on `/v1/runs` and
86
+ needs a hosted worker, which is not deployed on GCP; ordinary `ultrac` / `ultrac run` talk to `/v1/chat/completions` and
87
+ do not wait on that worker.
88
+
89
+ ### Endpoint and environment
90
+
91
+ The authenticated Ultrac API is served by the site under `/v1` (`/v1/chat/completions`, `/v1/models`,
92
+ `/v1/runs`, served model `cosmic-1`). The default base URL is `https://ultrac.io/v1`. Precedence,
93
+ highest first:
94
+
95
+ | Setting | Flag | Environment | Stored | Default |
96
+ | --- | --- | --- | --- | --- |
97
+ | API key | `login` (browser), `login --manual`, or `login --key <existing-key>` | `ULTRAC_API_KEY` | `~/.ultrac/auth.json` | - |
98
+ | Base URL | `--base-url <url>` (on `login`, `ask`, `run`, `task`/`tasks`, and the interactive session) | `ULTRAC_BASE_URL` | `~/.ultrac/auth.json` | `https://ultrac.io/v1` |
99
+ | Key page | - | `ULTRAC_LOGIN_URL` | - | `/keys` on the base URL's origin (`--manual` only) |
100
+
101
+ `ULTRAC_API_KEY` always wins over the stored key; `ultrac doctor` and `ultrac whoami` report which
102
+ source is in use. Use a CI secret manager to inject it for automation. For local development against
103
+ a site running on your machine, point the CLI at `http://localhost:3000/v1`:
104
+
105
+ ```bash
106
+ ultrac login --base-url http://localhost:3000/v1
107
+ # or, without saving anything:
108
+ ULTRAC_API_KEY="$LOCAL_ULTRAC_API_KEY" ULTRAC_BASE_URL=http://localhost:3000/v1 ultrac ask "say hello"
109
+ ```
110
+
111
+ Other variables: `ULTRAC_HOME` (global home directory, default `~/.ultrac`), `ULTRAC_MODEL`
112
+ (default model name, default `cosmic-1`), `ULTRAC_AST_GREP_BIN` (see Safety Model). The CLI reads
113
+ only `ULTRAC_*` names; there is no legacy fallback.
114
+
115
+ ## Use
116
+
117
+ Start an interactive session (this is the default command):
118
+
119
+ ```bash
120
+ ultrac
121
+ ```
122
+
123
+ Or seed it with a first prompt:
124
+
125
+ ```bash
126
+ ultrac "Find the main entry points and summarize the architecture"
127
+ ```
128
+
129
+ Inside a session:
130
+
131
+ - Type to talk to Ultrac; it gathers repository context first and can call tools.
132
+ - Gated tools (file writes, shell, network) prompt for approval: `once` / `always` / `no`.
133
+ - Coding commands: `/plan [task]`, `/coding`, `/review [focus]`, `/diff [--staged]`,
134
+ `/test [instructions]`, `/init`, `/max-steps [number]`.
135
+ - Context and sessions: `/context`, `/compact [focus]`, `/usage`, `/status`, `/save`,
136
+ `/sessions`, `/resume <id>`, `/new`, `/clear`.
137
+ - Controls: `/help`, `/model [name]`, `/mode [name]`, `/permissions [readonly|workspace|full]`,
138
+ `/allow <perm>`, `/deny <perm>`, `/whoami`, `/tools`, `/memory`, `/skills`, `/exit`.
139
+ Permission profiles change tool grants; they are not operating-system sandboxes. `readonly`
140
+ revokes write, shell and network access and suppresses permission prompts until explicitly granted.
141
+ `/usage` shows reported agent-step counts, excluding compaction and ancestor-session calls.
142
+ - Skills can add their own slash commands (see [Skills](#skills)). `/help` lists them; built-ins
143
+ always win a name clash.
144
+ - Transcripts are saved under `.ultrac/sessions` in the workspace on exit. They contain prompts
145
+ and tool results. On POSIX, a newly created sessions directory uses mode `0700` and atomic
146
+ session files use mode `0600`; saving into an existing directory does not change that directory's
147
+ mode.
148
+ - MCP tool results sent to the model are bounded previews. When archiving succeeds, the full
149
+ structured result is stored under `.ultrac/sessions/<session-id>.artifacts`; on POSIX, the
150
+ artifact directory is set to mode `0700` and new files are created with mode `0600`. These
151
+ permissions are local access controls, not encryption. The CLI does not change repository ignore
152
+ rules or automatically delete session data: exclude `.ultrac/sessions/` from version control and
153
+ manage retention yourself.
154
+
155
+ Non-interactive, scriptable one-shot runs:
156
+
157
+ ```bash
158
+ ultrac run "Review this patch" --mode review
159
+ ultrac run "Inspect this repo" --provider echo # offline smoke provider, no key
160
+ ultrac run --continue <session-id> "Follow up on the last result"
161
+ ```
162
+
163
+ One prompt, one reply, no tools or workspace context - the smallest end-to-end check of the wiring:
164
+
165
+ ```bash
166
+ ultrac ask "say hello" # streams the reply to stdout
167
+ ultrac ask "say hello" --base-url http://localhost:3000/v1 --model cosmic-1
168
+ ultrac ask "say hello" --provider echo # offline
169
+ ultrac ask "say hello" --json --no-stream # {"provider","model","text"}
170
+ ```
171
+
172
+ ### Tasks
173
+
174
+ Hosted background tasks are not deployed on GCP. Use `ultrac run` for testing.
175
+
176
+ A task is one model request that runs off the request path: the API queues it (`POST /v1/runs`),
177
+ a worker next to the model executes it and appends events, and the CLI follows the event stream
178
+ (`GET /v1/runs/<id>/events`, server-sent events). A task can take minutes, so the stream is built
179
+ to be left and rejoined: the server ends every connection at its own cap, and the CLI reconnects
180
+ with `Last-Event-ID` and misses nothing. Close the laptop, come back, `tasks follow` the same id.
181
+
182
+ ```bash
183
+ ultrac task "Summarize the trade-offs of event sourcing" # queue, then stream the answer
184
+ ultrac task --chat <chatId> "Follow up on the above" # attach the run to a dashboard chat
185
+ ultrac task --no-follow "Long job" # print the run id and return
186
+ ultrac task --json "Long job" # one JSON line per event
187
+ ultrac tasks # list your runs (id, status, age, title)
188
+ ultrac tasks show <id> # the record, with the output once done
189
+ ultrac tasks follow <id> # stream from the start (or --since <seq>)
190
+ ultrac tasks cancel <id> # stop a queued or running task
191
+ echo "a long prompt" | ultrac task - # read the prompt from stdin
192
+ ```
193
+
194
+ `ultrac tasks run` is the same command as `ultrac task`. While following, the answer streams
195
+ to stdout as it arrives and everything else - the run id, restarts, tool calls, the outcome - goes to
196
+ stderr, so `ultrac task "..." > answer.md` captures the answer alone. The exit code is the run's
197
+ outcome: `0` done, `1` failed, `130` cancelled. Ctrl-C asks the server to cancel the run and exits
198
+ `130`; with `--detach` it exits and leaves the run going, to be picked up again with `tasks follow`.
199
+ `--json` prints `{ "id", "event", "data" }` per event (the SSE id is the event's seq), the first line
200
+ being `"event": "created"` with the run, the last the terminal `done`, `error`, or `status` event.
201
+ A reclaimed attempt (`status: running` with `attempt > 1`) streams from the beginning again; the
202
+ CLI notes it on stderr and resets, the same way the dashboard does. `--no-follow` prints the id on
203
+ stdout, or the whole run with `--json`; `tasks show` and `tasks list` accept `--json` as well.
204
+
205
+ API errors print as one line with the stable code, e.g. `too_many_active_runs (HTTP 429): ...` when
206
+ the account already has 3 queued or running tasks, or `run_not_found (HTTP 404): ...` for an id that
207
+ is not yours. Tasks use the same key and base URL as every other command (see
208
+ [Endpoint and environment](#endpoint-and-environment)).
209
+
210
+ When a terminal event is marked `truncated: true`, the CLI reads the finished run record for its bounded
211
+ output. In JSON mode this adds an `output` event with `{ text, source: "run_record" }`. If the record
212
+ cannot be confirmed within the retry window, it adds `output_incomplete`, keeps the text received so
213
+ far, and exits non-zero rather than treating a prefix as complete.
214
+
215
+ ## Providers
216
+
217
+ - `ultrac` (default) - Cosmic 1 via the authenticated Ultrac API (`https://ultrac.io/v1`, model
218
+ `cosmic-1`). The API uses the OpenAI-compatible chat-completions wire format with SSE streaming
219
+ and native tool calling; this does not mean a separate OpenAI provider is supported. The hosted
220
+ Cosmic 1 model is live; an approved account and valid API key are required.
221
+ - `echo` - a deterministic local stub for smoke tests and offline wiring checks; no network, no
222
+ key, and no real model work. It is not a way to use the harness with another model or to get a
223
+ real model answer.
224
+
225
+ ### Fallback chain
226
+
227
+ `.ultrac/config.json` can name an ordered chain that retries and, as a last resort, falls back to
228
+ the offline `echo` stub after a retryable transport failure:
229
+
230
+ ```json
231
+ {
232
+ "provider": "ultrac",
233
+ "model": "cosmic-1",
234
+ "fallback": {
235
+ "chain": [{ "provider": "ultrac" }, { "provider": "echo" }],
236
+ "attemptsPerProvider": 3,
237
+ "baseDelayMs": 250,
238
+ "requestTimeoutMs": 60000
239
+ }
240
+ }
241
+ ```
242
+
243
+ `model` is optional per entry and defaults to that provider's default model. Only transport failures
244
+ - network errors, timeouts, HTTP 408, 429, and 5xx - retry with jittered backoff and then fail over.
245
+ Authentication (401/403) and bad-request (400) failures are terminal and do not fall through to a
246
+ different model. Their diagnostics show a safe category and HTTP status, not the provider's raw
247
+ message. A stream that fails *after* tokens have been emitted keeps its partial reply instead of
248
+ restarting on another model. The REPL prints a notice naming the model that took over, and its banner
249
+ names the entry that is actually answering rather than the configured chain head.
250
+ `requestTimeoutMs` defaults to 120,000 milliseconds for CLI configuration. It bounds a complete or
251
+ native-tool attempt and time-to-first-token for text streams; set it to `0` only when an upstream
252
+ deadline is guaranteed. Retry waits are cancelled immediately by Ctrl-C.
253
+
254
+ The current `not_configured` response for Cosmic 1 is terminal and does not fall through to `echo`.
255
+ If a configured transport fallback does use `echo`, its deterministic placeholder is not a model
256
+ answer or a completed task.
257
+
258
+ Provider failures show a safe category and, when available, an HTTP status rather than the provider's
259
+ response body or SDK error message. MCP startup status likewise reports only a failure phase and
260
+ allowlisted process error code; child stderr is drained and discarded. This does not redact
261
+ intentional tool output: shell stdout/stderr and MCP tool results are passed through to the model and
262
+ may be shown in the terminal.
263
+
264
+ An MCP tool-call timeout, in-flight cancellation, or transport loss reports an unknown outcome: the server may have completed
265
+ the action. The CLI does not retry automatically; after a timeout or cancellation it sends a best-effort cancellation
266
+ notification, which is cooperative and does not roll back an action. Verify remote state before
267
+ retrying. The CLI does not provide duplicate prevention or rollback guarantees for MCP side effects.
268
+ Cancellation before dispatch is reported as not sent.
269
+
270
+ Interactive turn cancellation also reaches process-backed tools. A pre-aborted call does not spawn;
271
+ an active command receives termination followed by forced process-group cleanup after a two-second
272
+ grace period on POSIX. Partial output is retained and marked cancelled. Descendants that escape the
273
+ original group are not guaranteed to stop; Windows descendant cleanup is best-effort. Cancellation
274
+ does not roll back files, Git changes, or remote actions, and is not a universal interrupt for every
275
+ tool's filesystem operation.
276
+
277
+ Native tool calling is selected from the *head* of the chain, because that is the entry that answers
278
+ until it breaks. When a turn reaches a later entry that has no native tool calling, the chain renders
279
+ the transcript and tool schemas into the text JSON tool protocol and parses the reply back into
280
+ structured calls instead of skipping the entry - chain order is your preference ranking, so a later
281
+ model is never promoted over an earlier one just for having a richer protocol. The REPL prints a
282
+ notice when that happens, so a session started with `native tools` never silently changes protocol.
283
+ No entry is ever passed over without an event.
284
+
285
+ ## Trusting a workspace
286
+
287
+ `.ultrac/config.json` lives in the repository, so whoever wrote the repository wrote it. Two of its
288
+ fields make the CLI act: `permissions` turns on write, shell or network without a prompt, and
289
+ `mcpServers` names commands to start. Those two are honoured only for a workspace you have trusted;
290
+ everything else in the file - model, mode, tools, budgets - applies regardless.
291
+
292
+ Opening an untrusted workspace whose config asks for either shows what it asks for and offers to
293
+ trust it; the record lives in `~/.ultrac/trusted-workspaces.json`, never in the repository. Approval
294
+ is bound to a fingerprint of the permission grants and normalized MCP launch settings, including
295
+ commands, arguments, environment values and passthrough names. Those values are not copied into the
296
+ trust store. Changing security-sensitive settings requires approval again; changing model, mode or
297
+ other unrelated settings does not. Older path-only approvals are withheld until renewed. `ultrac run`
298
+ never asks - it is the non-interactive entry point - so it withholds the grants and says so on
299
+ stderr; `--allow-*` flags still apply to that run, because those are your words rather than the
300
+ repository's.
301
+
302
+ ```bash
303
+ ultrac trust # allow this workspace's config to grant permissions and start servers
304
+ ultrac trust --revoke # stop
305
+ ultrac trust --list # workspaces with a current approval
306
+ ```
307
+
308
+ A server's `envPassthrough` cannot name a credential either way: the environment a server starts
309
+ with has every secret-shaped variable removed, and a passthrough list cannot ask for one back.
310
+
311
+ ## Long code generation
312
+
313
+ The Ultrac provider does not set an output-token cap. Input, tool schemas and output share the
314
+ model's finite context window. The SDK request timeout matches GCP's one-hour request lifetime;
315
+ SDK retries are disabled so the harness can cancel a failed GPU request before retrying it.
316
+
317
+ Native generation hands off one complete tool call at a time. As soon as the first call's full
318
+ JSON arguments and required fields arrive, the provider explicitly cancels the remaining GPU
319
+ generation, then returns that call to the permission-gated local executor. This prevents the model
320
+ from continuing to generate dependent reads and repeated overwrites before seeing tool results.
321
+ Large implementations can use repeated complete file edits or append sections; no code-length
322
+ limit is imposed by this handoff. A missing finish marker, malformed call, or a reported length
323
+ cutoff is an error rather than a completed action. Both input schemas and tool permissions remain
324
+ enforced locally.
325
+
326
+ ## Context budget
327
+
328
+ Cosmic 1 runs on a 262,144-token window - about 1,048,000 characters at 4 characters per token - and a
329
+ request that does not fit comes back as a 400, not as a shorter answer. The whole tool registry is 48
330
+ tools, including `ask_user`; their names, descriptions, and JSON Schemas serialise to 29,410 characters
331
+ when `--tools all` is selected. Three things keep a session inside the window and the model focused
332
+ on the tools it needs.
333
+
334
+ **Each mode advertises the tools it actually uses.** `--tools` selects the schemas sent to the model;
335
+ `plan` and `review` additionally enforce their mode allowlists at dispatch and disable workspace
336
+ writes. Other modes rely on the usual tool permission gates.
337
+
338
+ | mode | tools | characters |
339
+ | --- | --- | --- |
340
+ | `default` | 16 | 9,152 |
341
+ | `plan` | 12 | 7,339 |
342
+ | `review` | 12 | 8,312 |
343
+ | `coding` | 20 | 13,035 |
344
+ | `security` | 24 | 14,321 |
345
+ | everything (`--tools all`) | 48 | 29,410 |
346
+
347
+ `default` is the compact core - read, list, search, shell, edit, patch, `git status`/`git diff`,
348
+ todos, plus shared memory reading, skills, delegation and `ask_user`. `coding` adds tests, diagnostics, package scripts, and AST search; `security` adds local
349
+ evidence tools (hashes, strings, hex, secret scan, timeline, blame) and `fetch_url`; `plan` and
350
+ `review` enforce their fixed tool sets and disable workspace writes. `review` diagnostics still need
351
+ shell permission. `ultrac tools --mode coding` lists a mode's set, and `ultrac tools --budget` prints
352
+ the table above from the live registry.
353
+
354
+ Two escape hatches, on `ultrac` and `ultrac run`:
355
+
356
+ ```bash
357
+ ultrac --tools all # advertise the whole registry, as before
358
+ ultrac --tools read_file,search_files,git_diff # advertise exactly these
359
+ ```
360
+
361
+ The same thing in `.ultrac/config.json`, as `"tools": "all"` or `"tools": ["read_file", "git_diff"]`.
362
+
363
+ **Ordinary tool results are capped at 6,000 characters** before they go into the transcript. A result over
364
+ the cap keeps its head and ends in a marker saying how many characters were cut and how to reach the
365
+ rest; `read_file` prefers a line range over a hard cut, returning whole lines and naming the
366
+ `startLine` to continue from. The full output is still recorded in the saved session under
367
+ `.ultrac/sessions` - the cap is about what the model re-reads on every later turn. Change it with
368
+ `--max-tool-result-chars`, `ULTRAC_MAX_TOOL_RESULT_CHARS`, or `"maxToolResultChars"` in the config.
369
+ Successful `ask_user` answers are kept whole so a constraint at the end is not lost. They still
370
+ count against the context window, so an oversized request fails locally before it reaches the API.
371
+
372
+ **Every request is measured before it is sent.** The provider adds up the system prompt, the
373
+ serialised tools, and the transcript. Over 60% of the window, the harness first tries to compact
374
+ the oldest turns that would otherwise be dropped into a bounded `[compacted-context]` summary.
375
+ If summarising fails or there is not enough room, it falls back to dropping whole old turns, so an
376
+ assistant `tool_calls` entry and its `tool` replies are removed together. The system prompt, pinned
377
+ user messages (including follow-up corrections), successful `ask_user` results, and the turn being
378
+ answered are retained verbatim when they fit; if those required messages cannot fit the full window,
379
+ the request fails locally rather than silently dropping them. Startup workspace context and older
380
+ assistant/tool history may be summarized or dropped; generated summaries are not lossless records.
381
+ The budget uses a character estimate (about four characters per token), not the model's tokenizer.
382
+ The hard check keeps a 2% wire margin for tokenizer and request-envelope variance. You are told when
383
+ compaction is applied or skipped and when trimming starts. A request that still cannot fit fails
384
+ locally with the numbers - system, tools, history, window - instead of being sent to come back a 400.
385
+ In the REPL, `/context` runs the same planner used by the next request and reports system, tool, and
386
+ history estimates, selected and omitted messages, and prompt/window headroom.
387
+
388
+ Direct user messages and successful `ask_user` question/answer pairs are saved in order and replayed
389
+ as pinned user messages on `--continue`; they are not executable tool calls. Their order is kept,
390
+ but their exact placement among the old assistant/tool steps is not. Assistant/tool history may
391
+ still be summarized or dropped to fit the context window. `/clear` starts a new conversation. Older
392
+ sessions without a constraint ledger retain the known initial task plus the existing task summary
393
+ and tool-step restore; later direct messages and `ask_user` answers cannot be recovered from those
394
+ files.
395
+
396
+ The window defaults to 262,144 tokens and is set with `--context-tokens`, `ULTRAC_MODEL_CONTEXT_TOKENS`,
397
+ or `"contextTokens"` in the config, in that order of precedence. The session banner prints the
398
+ budget it started with:
399
+
400
+ ```
401
+ tools 16 (mode default) ≈ 9,152 chars · window 262,144 tokens · tool results capped at 6,000 chars
402
+ ```
403
+
404
+ Every `ask`, `run`, and interactive process creates a private `0600` JSONL lifecycle log under
405
+ `.ultrac/logs` and prints its path. It records correlated provider attempts, request heartbeats,
406
+ first chunks, retries, cancellation, compaction, tools, subagents, and checkpoints. Prompt text,
407
+ tool arguments/results, request bodies, headers, and credentials are replaced by lengths and
408
+ SHA-256 fingerprints; they are never written raw. The directory is ignored by workspace discovery
409
+ and Git.
410
+
411
+ For a more detailed opt-in context audit trail, set `ULTRAC_CONTEXT_REPORT_PATH` to a JSONL file path. Each model request
412
+ records roles, message lengths and SHA-256 fingerprints, selected/omitted and pinned counts, compaction
413
+ source counts, native tool-schema count and fingerprint, estimated budget, truncation-marker counts,
414
+ duration, and partial file-read provenance for structured `read_file` and `read_many_files` results.
415
+ The file section distinguishes results kept in the provider request from results dropped by its budget
416
+ planner, and reports source ranges/byte limits plus whether the transcript cap may have cut the result.
417
+ It does not enumerate the repository or claim that unlisted files were excluded. Startup context,
418
+ search results, shell/MCP output, compaction summaries, and older results without structured metadata
419
+ are counted as unknown rather than attributed to specific files.
420
+
421
+ By default the report never writes prompt, transcript, tool-schema text, or file paths. To include
422
+ validated workspace-relative paths in the file section, separately set `ULTRAC_CONTEXT_REPORT_PATHS=1`.
423
+ Paths may reveal proprietary project structure; the report file is created with mode `0600` either way.
424
+ Message fingerprints can still identify known text, so keep the report private. Metadata does not
425
+ reveal the exact text sent, and this partial provenance is not a complete file-access log.
426
+
427
+ For opt-in freshness diagnostics, also set `ULTRAC_CONTEXT_STALE_CHECK=1`. The setting has no effect
428
+ without `ULTRAC_CONTEXT_REPORT_PATH`. Before a request, Ultrac rechecks only structured reads that
429
+ survive that request's context budget plan. The report records `fresh`, `changed`,
430
+ `missing_or_unreadable`, or `unknown` statuses and counts, not file hashes or paths. This check is
431
+ limited to the exact selected read payload (a line range or byte prefix), not the whole file or a
432
+ version lock; edits outside that payload may still report `fresh`, and a file can change after the
433
+ check. Legacy/restored, transcript-capped, unstable, and otherwise unsupported reads are `unknown`.
434
+ Revalidation reads at most 1 MiB per request and stops after 500 ms; timed-out work may finish in the
435
+ background. The diagnostic does not refresh context or prevent the model from acting on stale text.
436
+
437
+ ## Memory
438
+
439
+ Ultrac keeps a file-based memory, like Claude Code, so durable facts and preferences survive across
440
+ sessions. One fact per Markdown file with YAML frontmatter, plus a `MEMORY.md` index that is small
441
+ enough to load into the agent's context every turn. Two scopes:
442
+
443
+ - **global** - `~/.ultrac/memory/` (shared across every workspace)
444
+ - **project** - `.ultrac/memory/` (checked in with the repo)
445
+
446
+ ```bash
447
+ ultrac memory list # index across both scopes
448
+ ultrac memory add no-pill --description "UI rule" --type feedback --body "No pill+dot indicators."
449
+ echo "Use aws login for tokens." | ultrac memory add aws-auth --description "AWS auth" --scope global
450
+ ultrac memory show no-pill
451
+ ultrac memory search composer
452
+ ultrac memory remove no-pill
453
+ ```
454
+
455
+ During a session the agent sees the memory index automatically and pulls a specific memory in full
456
+ with the `memory_read` tool. It persists new durable facts with `memory_write` (needs `--allow-write`),
457
+ and can prune stale ones with `memory_delete`. Types are `user`, `feedback`, `project`, `reference`.
458
+
459
+ ## Skills
460
+
461
+ Every installation includes eight built-in playbooks: `coding-workflow`, `debugging`, `testing`,
462
+ `security-review`, `refactoring`, `release-readiness`, `frontend-quality`, and `backend-reliability`.
463
+ They are embedded in the CLI artifact, work offline, and require no separate skill downloads.
464
+ Use `ultrac skills list` and `ultrac skills show coding-workflow` to inspect them.
465
+ Skills supply instructions; they do not grant shell, write or network access or provide a model backend.
466
+
467
+ Personal skills override built-ins; trusted project skills override personal skills. Untrusted
468
+ project skills cannot hide installed playbooks or register slash commands. Skill files are limited
469
+ to 64 KiB, with at most 128 directories per custom scope. Symlinked skill directories/files and
470
+ sensitive paths are skipped. Run `ultrac trust` only for a repository whose instructions you accept.
471
+
472
+ Skills are reusable SKILL.md playbooks the agent can load on demand - the same idea as Claude Code's
473
+ custom skills. Each skill is a folder with a `SKILL.md` (YAML frontmatter + Markdown instructions):
474
+
475
+ ```
476
+ ~/.ultrac/skills/<name>/SKILL.md # global (personal, every workspace)
477
+ .ultrac/skills/<name>/SKILL.md # project (checked in; overrides a global skill of the same name)
478
+ ```
479
+
480
+ ```bash
481
+ ultrac skills create open-pr --description "Open a pull request" \
482
+ --when "the user asks to open a PR" --permissions shell,network
483
+ ultrac skills list
484
+ ultrac skills show open-pr
485
+ ```
486
+
487
+ Only each skill's name + description are injected into context (progressive disclosure). When a task
488
+ matches, the agent calls the `skill` tool to load the full steps, then follows them. `list_skills`
489
+ enumerates what is available.
490
+
491
+ ### Custom slash commands
492
+
493
+ A skill that adds a `command:` key to its frontmatter is also callable as a REPL slash command:
494
+
495
+ ```yaml
496
+ ---
497
+ name: open-pr
498
+ description: Open a pull request with a generated title and body.
499
+ command: open-pr
500
+ argument-hint: <branch>
501
+ ---
502
+ ```
503
+
504
+ Typing `/open-pr feature-x` seeds the turn with the skill's instructions plus the verbatim arguments.
505
+ Built-in commands always win a name clash, project scope outranks global scope, and malformed or conflicting command
506
+ declarations appear under `/help`. Untrusted or unsafe files are skipped before reading their declarations.
507
+
508
+ ## Safety Model
509
+
510
+ The CLI treats the current workspace as the trust boundary.
511
+
512
+ - Paths are resolved inside the workspace root.
513
+ - `inspect_project`, `repo_map`, `repo_handbook`, `context_bundle`, `verification_plan`,
514
+ `verification_report`, `read_file`, `read_many_files`, `list_files`, `search_files`,
515
+ `ast_search`, `inspect_artifact`, `hex_dump`, `extract_strings`, `scan_secrets`,
516
+ `file_timeline`, `task`, Git inspection tools (including `git_blame`), and session search tools
517
+ are available by default. `ast_search`, `inspect_artifact`, `extract_strings`, and `task` are
518
+ callable with no permission, but each degrades rather than escalating: `ast_search` answers from
519
+ its text fallback until `--allow-shell` unlocks the `ast-grep` binary, `inspect_artifact` and
520
+ `extract_strings` use a Node implementation until `--allow-shell` unlocks `file`/`strings`, and
521
+ `task` keeps its child transcript in memory until `--allow-write` lets it be persisted.
522
+ - Secret-like files such as `.env*`, `.npmrc`, `.netrc`, `.ssh/*`, `.aws/credentials`, `*.pem`,
523
+ `*.key`, `*.p12`, and `*.pfx` are hidden from listings and refused by direct file reads.
524
+ - `git_worktree_summary` reports branch, upstream divergence, dirty-file counts, and diff stats
525
+ without shell permission.
526
+ - Every Git tool spawns `git` with repo-controlled command execution disabled. A repository can
527
+ name a command in its own `.git/config` that git then runs - `core.fsmonitor` on `git status`,
528
+ `diff.external` on `git diff` - which would be arbitrary execution under tools that declare no
529
+ permissions and are handed to read-only subagents. All Git invocations go through one helper that
530
+ prepends neutralising `-c` flags (`core.fsmonitor`, `core.hooksPath`, `core.pager`, `core.askPass`,
531
+ `core.sshCommand`, `core.gitProxy`, `credential.helper`, `diff.external`, `protocol.ext.allow`,
532
+ `uploadpack.packObjectsHook`), which override both repo and global config.
533
+ - `verification_plan` reads Git state and package scripts to recommend the checks an agent should
534
+ run before claiming completion. It does not execute project scripts.
535
+ - `context_bundle` packages selected files, repository shape, Git state, and task instructions into
536
+ a Markdown or JSON context artifact. Rendering is read-only; saving to `.ultrac/context` requires
537
+ `--allow-write`.
538
+ - `verification_report` renders Git evidence, check outcomes, and handoff notes into a Markdown or
539
+ JSON report. Writing the report to `.ultrac/reports` requires `--allow-write`.
540
+ - `multi_edit` requires `--allow-write` and applies several exact replacements to one file atomically (all-or-nothing).
541
+ - `memory_read`, `memory_list`, `memory_search`, `list_skills`, `skill`, `todo_write`, and `todo_read` are read-only and available by default; `memory_write` and `memory_delete` require `--allow-write`.
542
+ - `write_file` requires `--allow-write`.
543
+ - `replace_in_file` requires `--allow-write` and refuses ambiguous single replacements.
544
+ - `apply_patch` requires `--allow-write` and applies unified diffs inside the workspace.
545
+ - `git_commit` requires `--allow-write` and stages either selected paths or all worktree changes; any files
546
+ already staged anywhere in the repository are included in the commit. Before staging, it checks the full
547
+ existing index plus the requested candidate paths for known sensitive names (`.env`, `.env.*`, `.netrc`,
548
+ `.npmrc`, `.pypirc`, `.git-credentials`, private-key names/extensions, non-public `.ssh` paths,
549
+ `.git/config`, `.aws/credentials`, and `.ultrac/config.json`) and generated
550
+ `.ultrac/{sessions,archives,episodes,subagents,context,reports}` artifacts. It refuses when any effective
551
+ Git clean/process filter is configured because staging could transform content
552
+ or execute a command. This is a path-name guard, not secret-content scanning, and it does not isolate against
553
+ concurrent filesystem changes; inspect and stage intentionally outside the tool when it refuses.
554
+ - `git_push` requires `--allow-network` and pushes a named or current branch.
555
+ - `ast_search` matches code by syntax-tree shape using the optional `ast-grep` binary. Calling it
556
+ needs no permission, but running that binary - including the `--version` probe used to detect it -
557
+ is a child process and requires `--allow-shell`. Without shell, or with no binary installed, it
558
+ spawns nothing and degrades to the `search_files` text path, reporting `backend: "text-fallback"`
559
+ plus a `degradedReason` so approximate results are not over-trusted. `ULTRAC_AST_GREP_BIN`
560
+ overrides binary detection; the value must be a path made only of `[A-Za-z0-9_./+@:-]`, with no
561
+ `..` segment, resolving to an existing regular file (relative values resolve against the
562
+ workspace, so a bare name is a workspace file, not a `PATH` lookup - unset the variable to get the
563
+ `PATH` search of `ast-grep`/`sg` back). A refused override degrades to the fallback and reports
564
+ why.
565
+ - `task` spawns a read-only child agent with its own context window for search-heavy exploration or
566
+ a focused review pass, and returns only its summary. The child is always read-only regardless of
567
+ what the parent holds, cannot spawn its own subagent, and is bounded by turn, tool-call, and
568
+ wall-clock caps. Its full transcript is always recorded in the parent's saved session for audit,
569
+ and is additionally written to `.ultrac/subagents` when the session holds `--allow-write`; a
570
+ read-only session keeps it in memory only and leaves the workspace untouched.
571
+
572
+ Several `task` calls in one model step run **concurrently**, so five independent searches cost
573
+ about as long as the slowest one rather than the sum of all five. While they run, the terminal
574
+ shows a live line - how many have finished, how long it has been, what it has spent:
575
+
576
+ ```
577
+ ○ ultrac task ×6 2/6 agents done · 1m 04s · ↓ 128.4k tokens
578
+ ```
579
+
580
+ Concurrency is decided per tool and conservatively: a call may overlap its neighbours only when it
581
+ needs neither write nor shell access. Anything that writes runs alone, and results are always
582
+ recorded in the order the model asked for them, so what the model reads back never depends on
583
+ which call happened to finish first. Children are one level deep - a subagent still cannot spawn
584
+ its own.
585
+
586
+ Each model response can request at most 30 tool calls. A top-level one-shot run or interactive
587
+ turn can dispatch at most 120 top-level calls; each subagent run has its own budget. An over-budget
588
+ response runs none of its tools and ends with a clear error. Each run's safe batch is limited to 8
589
+ concurrent calls, with results kept in request order; this is not a process-wide cap because
590
+ subagents run their own batches. Provider failover does not reset a turn's budget.
591
+ - `run_package_script` requires `--allow-shell` and only runs scripts declared in `package.json`.
592
+ - `run_tests` requires `--allow-shell`. It detects the project's runner (vitest, jest, `node --test`,
593
+ pytest, `cargo test`, `go test`) and returns pass/fail counts plus per-failure records with file,
594
+ line, expected/actual, a category, and a likely cause instead of raw stdout.
595
+ - `diagnostics` requires `--allow-shell`. It runs the project's type checker (and a cheap linter when
596
+ one is installed) and returns deduplicated `{file, line, column, code, severity, message}` records.
597
+ Pass `paths` to scope results to the files you just edited.
598
+ - `run_shell` requires `--allow-shell`. It refuses a short list of obviously destructive command
599
+ patterns (`rm -rf`, `git reset --hard`, `sudo`, `mkfs`, fork bombs, curl-piped-to-shell), which is a
600
+ speed bump and not a sandbox: the command reaches a real shell, and a denylist over shell syntax can
601
+ always be worked around. `--allow-shell` grants broad, unsandboxed shell access; commands run as
602
+ your user and can access files and network resources available to your account. Use it only in a
603
+ workspace you trust.
604
+ - Child-process tools redact common API keys, cloud credentials, tokens, and SSH agent handles from
605
+ the environment before running project code.
606
+ - `fetch_url` requires `--allow-network`. It is an HTTP(S) client for a URL you name (GET by
607
+ default; POST/PUT/PATCH/DELETE with a body), follows bounded redirects, and blocks loopback and
608
+ private addresses. That block is not something the model can switch off - set
609
+ `ULTRAC_ALLOW_PRIVATE_HOSTS=1` in the environment if you need it, which is a decision you make,
610
+ not the agent. One request per call; it is not a port scanner.
611
+ - `list_processes` requires `--allow-shell` and runs `ps` with a fixed argument list on this
612
+ machine. It is host inventory, not a remote scan.
613
+ - `hex_dump` and `extract_strings` refuse the same sensitive paths `read_file` does.
614
+ `scan_secrets` reports that those files exist without opening them, and redacts every match it
615
+ does return from source.
616
+ - Session transcripts are stored under `.ultrac/sessions` in the target workspace.
617
+ - A `--continue` session records its immediate parent and replays the bounded saved lineage (up to
618
+ 32 ancestors and 64 MiB of session files). Keep parent session files if you need to continue the
619
+ full history; missing parents or limits stop continuation instead of silently shortening it.
620
+ - `ultrac sessions list|show|search` makes those transcripts inspectable from the terminal.
621
+ - `ultrac sessions export <id>` turns one transcript into a Markdown or JSON episode package for
622
+ audits, evals, and handoffs. An export contains only that session and names any parent dependency;
623
+ it does not bundle the parent transcript. Exports can include `--outcome` and `--notes`, and record
624
+ saved context bundles and verification report artifacts used during the run.
625
+
626
+ ## Local package smoke (maintainers)
627
+
628
+ To check a locally built tarball before release, run `npm pack` and pass the resulting archive to
629
+ the repository smoke script:
630
+
631
+ ```bash
632
+ archive="$(npm pack --silent)"
633
+ node scripts/smoke-package-install.mjs "$archive"
634
+ ```
635
+
636
+ The script currently supports POSIX systems. It installs that `.tgz` into a temporary prefix with
637
+ temporary home and npm config, disables lifecycle scripts and network access, checks the installed
638
+ package and `ultrac` binary, runs only `--version` and `--help`, uninstalls the package, and removes
639
+ the temporary directory. It uses the existing `~/.npm` cache when present; set
640
+ `ULTRAC_NPM_CACHE` to another pre-populated npm cache if needed. Missing offline dependencies are
641
+ reported as a prerequisite; the script never falls back to the network. The smoke checks only a
642
+ trusted local build: it does not establish archive signature or provenance, and it is not a sandbox
643
+ for untrusted packages. The script is repository-only and is not included in the published tarball.
644
+
645
+ ## Documentation and support
646
+
647
+ Product documentation lives at [ultrac.io/docs](https://ultrac.io/docs). Bugs, questions, and
648
+ access requests go through [ultrac.io/contact](https://ultrac.io/contact). There is no public issue
649
+ tracker: the source repository is private.
650
+
651
+ ## Licence
652
+
653
+ `@spinalfluid/ultrac` is proprietary software, copyright Aditya Pundir. Installing it gives you a
654
+ revocable, non-transferable right to *run* the published build - nothing more. Copying,
655
+ modification, redistribution, and reverse engineering are reserved. The full terms are in the
656
+ `LICENSE` file that ships inside the package, and they are the whole agreement: nothing on this
657
+ page grants a right the licence withholds.
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ #!/usr/bin/env node