atom-agent 1.2.0 → 1.3.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/CHANGELOG.md +75 -0
- package/README.md +13 -4
- package/atom.example.json +11 -0
- package/dist/App.js +923 -200
- package/dist/adapters.js +82 -13
- package/dist/agent/goal-evaluator.js +69 -0
- package/dist/agent/loop.js +517 -76
- package/dist/cli.js +11 -3
- package/dist/compact.js +41 -15
- package/dist/config.js +43 -7
- package/dist/context-manager.js +16 -198
- package/dist/context-windows.js +4 -2
- package/dist/env-block.js +5 -5
- package/dist/extension-commands.js +196 -0
- package/dist/extension-ui.js +153 -0
- package/dist/extensions.js +1571 -0
- package/dist/goal.js +583 -0
- package/dist/project-trust.js +96 -0
- package/dist/providers.js +6 -6
- package/dist/scheduler.js +74 -36
- package/dist/session.js +23 -5
- package/dist/sessions.js +25 -6
- package/dist/telemetry-dashboard.js +28 -0
- package/dist/telemetry.js +39 -0
- package/dist/tools/compaction-hooks.js +165 -0
- package/dist/tools/custom.js +189 -0
- package/dist/tools/intercept.js +145 -0
- package/dist/tools/overrides.js +105 -0
- package/dist/tools/provider-hooks.js +224 -0
- package/dist/tools/registry.js +246 -17
- package/dist/tools.js +44 -0
- package/dist/ui/palette.js +1 -1
- package/dist/ui/status-bar.js +80 -5
- package/dist/zen.js +305 -75
- package/documentation/architecture.md +114 -0
- package/documentation/cli.md +82 -0
- package/documentation/compaction.md +50 -0
- package/documentation/configuration.md +111 -0
- package/documentation/development.md +62 -0
- package/documentation/extensions.md +160 -0
- package/documentation/getting-started.md +63 -0
- package/documentation/goals.md +41 -0
- package/documentation/index.md +41 -0
- package/documentation/observability.md +70 -0
- package/documentation/permissions.md +66 -0
- package/documentation/providers.md +78 -0
- package/documentation/sessions.md +92 -0
- package/documentation/skills.md +57 -0
- package/documentation/tools.md +94 -0
- package/documentation/troubleshooting.md +54 -0
- package/examples/extensions/01-audit-gate.js +24 -0
- package/examples/extensions/02-notes-tool.js +32 -0
- package/examples/extensions/03-custom-command.js +32 -0
- package/package.json +6 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Goals
|
|
2
|
+
|
|
3
|
+
One pinned session goal that keeps the agent working turn-to-turn until it is done, stuck, paused, or cleared.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
| Input | Effect |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `/goal <objective>` | Pin the objective (replacing any live goal resets its counters) |
|
|
10
|
+
| `/goal` (bare) | Show text, state (`active`/`paused`), and cumulative stats (turns · requests · tokens · work) |
|
|
11
|
+
| `/goal pause` | Halt the run; objective and stats are kept |
|
|
12
|
+
| `/goal resume` | Re-arm a paused goal: when idle it starts a continuation turn; when busy it resumes at the current turn's end (no turn is injected while busy) |
|
|
13
|
+
| `/goal clear` | End the goal |
|
|
14
|
+
|
|
15
|
+
## How the run works
|
|
16
|
+
|
|
17
|
+
- **No turn cap.** The goal continues turn-to-turn until it is paused, cleared, ends in a `complete`/`blocked` verdict, or a thrown failure stops the turn.
|
|
18
|
+
- **Pause never clears.** Cancel (`Esc`/`Ctrl+C`) and spent step/tool-call budgets pause the goal with its objective, stats, todos, and history intact; `/goal resume` continues. Only `/goal clear`, `/clear`, and `/new` end it (`/clear` and `/new` wipe the conversation, so the goal cannot survive them).
|
|
19
|
+
- **The model reports each turn** with the goal-scoped `update_goal` tool: `continue` with the next action, or `complete`/`blocked` with a reason. Only the first terminal report per turn sticks; calls outside a goal turn record nothing.
|
|
20
|
+
- **Report-less turns** get one bounded judge call when a judge is configured, otherwise the goal continues. An unclear or failed judge pauses with the goal preserved.
|
|
21
|
+
- **Stall redirect.** Three consecutive repeated tool results push a replan nudge instead of repeating; the goal stays active.
|
|
22
|
+
- **Honest completion.** A `complete` with unverified code changes or open todos continues the turn instead of stopping; `blocked` stops unconditionally. Checks the model could not run ride the `complete` report as `unverified` (at most 10 items, 200 chars each) and print openly in the closing verdict — recorded, never a gate.
|
|
23
|
+
|
|
24
|
+
## Surfacing
|
|
25
|
+
|
|
26
|
+
- **Status line:** `goal: <objective> [active|paused]` while a goal is live (truncated to fit; lowest-priority segment — it drops before anything else moves, and hides entirely with no goal).
|
|
27
|
+
- **Telemetry:** each turn trace carries the live goal (objective, state, counters); the dashboard shows a per-turn goal fragment and a Goal-turns overview card only when goal turns exist.
|
|
28
|
+
- **Persistence:** the live goal rides every session save with its stats intact — `/resume` and session switches restore it; corrupt data loads as no goal.
|
|
29
|
+
- **Compaction:** the summary gains a `Goal:` line (text, state, stats, open todos) as the model's context backstop; record restore stays the restore path.
|
|
30
|
+
|
|
31
|
+
## Known limits
|
|
32
|
+
|
|
33
|
+
- The `update_goal` schema is not in the chat-payload `tools` list — the model discovers it through the continuation message prose, not a tool definition.
|
|
34
|
+
- Multi-turn goal behavior against live models is unproven; the loop, judge, and gate paths are covered by mocked suites.
|
|
35
|
+
|
|
36
|
+
## Code
|
|
37
|
+
|
|
38
|
+
- State machine, notices, stats, judge parsing, stall guard, persistence shape, compaction block: `src/goal.ts`
|
|
39
|
+
- Commands, resume kickoff, stats accrual, session save/restore: `src/App.tsx` (`runGoalCommand`, `submit`)
|
|
40
|
+
- Turn-end protocol, evaluator fallback, stall redirect, honesty gate: `src/agent/loop.ts`
|
|
41
|
+
- Evaluator transport: `src/agent/goal-evaluator.ts`
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# ATOM Documentation
|
|
2
|
+
|
|
3
|
+
Minimal AI coding agent for your terminal. Agentic loop, 13 local tools, streaming Ink TUI, 8 remote providers plus 3 local runtimes behind one UI (Kilo Gateway default, key-optional).
|
|
4
|
+
|
|
5
|
+
This index is the entry point. The README stays focused on evaluate, install, and first run. Everything deeper lives here.
|
|
6
|
+
|
|
7
|
+
## Start here
|
|
8
|
+
|
|
9
|
+
- [Getting Started](getting-started.md) - install, key-optional first run, quickstart path
|
|
10
|
+
- [CLI and TUI](cli.md) - slash commands, keyboard, status line, autocomplete
|
|
11
|
+
- [Goals](goals.md) - pinning a session goal that runs turn-to-turn until done, stuck, paused, or cleared
|
|
12
|
+
|
|
13
|
+
## Core concepts
|
|
14
|
+
|
|
15
|
+
- [Tools](tools.md) - the 13 local executors, caps, approval classes, background tasks
|
|
16
|
+
- [Providers and Models](providers.md) - 8 remote providers + 3 local runtimes, endpoints, key resolution, model pickers
|
|
17
|
+
- [Permissions and Modes](permissions.md) - normal/yolo/plan, trust, allow/deny rules
|
|
18
|
+
- [Skills](skills.md) - discovery, frontmatter contract, precedence, auto-invoke
|
|
19
|
+
- [Extensions](extensions.md) - zero-to-running guide plus the working sample gallery
|
|
20
|
+
|
|
21
|
+
## Sessions and context
|
|
22
|
+
|
|
23
|
+
- [Sessions](sessions.md) - persistence file, resume, clear, rewind
|
|
24
|
+
- [Compaction and Token Display](compaction.md) - auto-compact threshold, manual compact, footer format
|
|
25
|
+
- [Observability](observability.md) - local telemetry, /telemetry, dashboard drill-down
|
|
26
|
+
- [Configuration](configuration.md) - env vars, auth file, AGENTS.md layering
|
|
27
|
+
|
|
28
|
+
## Build and fix
|
|
29
|
+
|
|
30
|
+
- [Development](development.md) - scripts, project structure, tests, build output
|
|
31
|
+
- [Architecture](architecture.md) - module map, dependency directions, boundary rules
|
|
32
|
+
- [Troubleshooting](troubleshooting.md) - common failures and what to check first
|
|
33
|
+
|
|
34
|
+
## Agent docs (existing)
|
|
35
|
+
|
|
36
|
+
Project conventions the agent itself loads at runtime:
|
|
37
|
+
|
|
38
|
+
- [AGENTS.md](../AGENTS.md) - agent instructions loaded into the system prompt
|
|
39
|
+
- [Issue tracker](agents/issue-tracker.md) - local markdown issues under `.scratch/`
|
|
40
|
+
- [Triage labels](agents/triage-labels.md) - canonical triage roles
|
|
41
|
+
- [Domain docs](agents/domain.md) - CONTEXT.md plus ADR conventions
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
Local-only agent telemetry plus a drill-down dashboard. No accounts, no remote servers, no uploads — traces stay on your machine under `~/.atom/telemetry/`.
|
|
4
|
+
|
|
5
|
+
## Quickstart
|
|
6
|
+
|
|
7
|
+
- Use the agent normally. Every completed, failed, or cancelled turn appends its trace to the current session file.
|
|
8
|
+
- `/telemetry` — one-line summary: this session plus stored totals (sessions, turns, model/tool calls, success rate, reported tokens, retries).
|
|
9
|
+
- `/dashboard` — writes `~/.atom/telemetry/dashboard.html` and prints the path. Open it in a browser.
|
|
10
|
+
- `atom --dashboard` — same page without starting the TUI (script it, e.g. after a run).
|
|
11
|
+
- `atom --serve [--port <n>]` — live webUI on loopback: the same dashboard re-rendered per request (auto-refreshes) plus a read-only JSON API. Ctrl+C stops. Nothing is written over HTTP.
|
|
12
|
+
|
|
13
|
+
## What is traced
|
|
14
|
+
|
|
15
|
+
One session file per App mount (`~/.atom/telemetry/sessions/<sessionId>.json`, `0600` POSIX, atomic temp-plus-rename writes on turn boundaries):
|
|
16
|
+
|
|
17
|
+
- **Session** — id, start/end timestamps, project, provider/model.
|
|
18
|
+
- **Turn** — one user message plus its full loop: input/reply previews, provider/model/effort/mode, outcome (`completed`, `blocked`, `unverified`, `budget-exceeded`, `failed`, `cancelled`), duration, accumulated token usage. Turns started with a live goal also carry the goal snapshot (objective, active/paused, cumulative counters); the dashboard renders a per-turn goal fragment plus a Goal-turns overview card only when goal turns exist (see [Goals](goals.md)).
|
|
19
|
+
- **Iteration** — one loop tool-round step (displayed 1-based): its model call plus the tool calls that call requested, with a timeline bar.
|
|
20
|
+
- **Model call** — one chat POST: latency, finish reason (`final`, `tool_calls`, `error`), reasoning label when the response carried one, per-call token usage **only when the provider sent a `usage` payload**, and transport retries (attempt, delay, HTTP status).
|
|
21
|
+
- **Tool call** — one execution: tool name, measured dispatch→result duration, success/failure with kind (`unknown-tool`, `invalid-args`, `denied`, `tool-error`, `cancelled`, `transport-error`), scrubbed + truncated args/result previews with full sizes, parallel-batch position.
|
|
22
|
+
- **Session events** — `/clear`, `/new`, `/resume`, compactions, provider/model switches.
|
|
23
|
+
- **Subagents** — delegated workers. ATOM v1 runs a single-agent loop (depth 1), so this is normally empty and the dashboard says so; the schema is ready for a future delegate tool.
|
|
24
|
+
|
|
25
|
+
The dashboard adds aggregates (totals, per-tool tables, success rate, average latencies), SVG charts (tool calls by tool, tokens per session, outcomes, durations), text/provider/outcome filters, and per-turn timelines.
|
|
26
|
+
|
|
27
|
+
## WebUI (live local server)
|
|
28
|
+
|
|
29
|
+
`atom --serve` starts a read-only HTTP server (`node:http` builtin, no new dependencies) over the same store:
|
|
30
|
+
|
|
31
|
+
- `GET /` — the dashboard, re-rendered per request with a 5s auto-refresh pill, so new flushed turns appear without regenerating a file.
|
|
32
|
+
- `GET /api/health` — liveness plus session/turn counts.
|
|
33
|
+
- `GET /api/aggregates` — the same totals the page shows, as JSON.
|
|
34
|
+
- `GET /api/sessions` — per-session summaries; `GET /api/sessions/:id` — one full stored session (404 when unknown).
|
|
35
|
+
|
|
36
|
+
Rules that keep it safe and honest:
|
|
37
|
+
|
|
38
|
+
- **Loopback-only** (`127.0.0.1`). The server never binds a LAN interface unless explicitly asked, and the JSON API carries the same scrubbed previews as the page — never keys.
|
|
39
|
+
- **Read-only**: only GET is served (anything else is 405); no request body is read; nothing is ever written. Every request re-reads the store, so the view is always current.
|
|
40
|
+
- **Ephemeral port by default** (printed on start, e.g. `http://127.0.0.1:52314/`). Pin one with `atom --serve --port 3487` or `ATOM_TELEMETRY_PORT=3487` when bookmarkable matters; a taken port fails fast with a hint.
|
|
41
|
+
- Same n/a discipline as the static page — the JSON carries reported values plus `usageReported`-style flags, never zero-filled estimates.
|
|
42
|
+
|
|
43
|
+
## Honesty rules (read before quoting numbers)
|
|
44
|
+
|
|
45
|
+
- **n/a means not measured or not reported — never zero.** Hover any n/a for the exact reason.
|
|
46
|
+
- **Tokens are API-reported only.** A model call without a `usage` payload contributes nothing; sessions without payloads are excluded from token charts (not plotted as zero). The footer `token: n/a` and this page agree by construction.
|
|
47
|
+
- **Cost is always n/a.** No provider API reports cost, and there is deliberately no pricing table — tokens are never multiplied by invented prices.
|
|
48
|
+
- **Tools show no token counts.** Tools don't consume model tokens; usage lives on model calls and turn/session aggregates.
|
|
49
|
+
- **Tool duration spans dispatch→result**, including any approval-prompt wait in normal mode (yolo/plan-mode calls measure execution only). The dashboard footnotes this wherever durations appear.
|
|
50
|
+
- **Retries** are transport retries inside one model call (HTTP 429/5xx or network, up to 10 with 1s→2s→4s… backoff honoring Retry-After) and attach to the call they precede.
|
|
51
|
+
|
|
52
|
+
## Privacy
|
|
53
|
+
|
|
54
|
+
- Previews are truncated (input/reply 500 chars, args/results 2000 chars) with full byte sizes shown, so truncation is never silent.
|
|
55
|
+
- Known provider secrets (live env values) are scrubbed to `[redacted]` before anything is stored. API keys are never stored. Full prompts, full file contents, and full tool results are never persisted.
|
|
56
|
+
- Telemetry files live outside the repo (`~/.atom/`, never committed). The dashboard is a static file with no external requests — it works over `file://` with the network off.
|
|
57
|
+
- Opt out entirely: `ATOM_TELEMETRY=0` (env wins) or `"telemetry": {"enabled": false}` in `atom.json`. When off, every recorder method is a no-op and nothing is written. Corrupt session files are counted and skipped, never crash the page.
|
|
58
|
+
|
|
59
|
+
## Performance
|
|
60
|
+
|
|
61
|
+
Recording is in-memory pushes plus `Date.now()` reads — no I/O in the turn hot path. The only disk write is one small atomic JSON file per turn boundary (typically a few KB; previews are capped). Retention prunes on flush (default: newest 200 sessions, 90 days).
|
|
62
|
+
|
|
63
|
+
## Files and code
|
|
64
|
+
|
|
65
|
+
- Recorder + store + aggregates: `src/telemetry.ts` (never throws; disabled mode is a no-op).
|
|
66
|
+
- Loop hooks: optional `telemetry` sink in `AgenticOpts` (`src/zen.ts`) — guarded, zero behavior change when absent.
|
|
67
|
+
- Dashboard renderer + writer: `src/telemetry-dashboard.ts` (pure builder; self-contained HTML, no dependencies).
|
|
68
|
+
- Wiring: `submit()` in `src/App.tsx` opens/closes turn traces; `/telemetry` and `/dashboard` commands; `--dashboard` / `--serve` CLI flags in `src/cli.tsx`.
|
|
69
|
+
- Live server: `src/telemetry-server.ts` (loopback-only, read-only GET, per-request store re-read; same builder + honesty rules as the static page).
|
|
70
|
+
- Tests: `tests/telemetry.test.ts` (recorder, store, aggregates, dashboard escaping, loop sink, config knob, TUI end-to-end) and `tests/telemetry-server.test.ts` (port parsing, routes, live re-read, close).
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Permissions and Modes
|
|
2
|
+
|
|
3
|
+
Three modes plus scoped session rules. No path sandbox: the permission system is the control plane.
|
|
4
|
+
|
|
5
|
+
## Modes
|
|
6
|
+
|
|
7
|
+
Default is `normal`:
|
|
8
|
+
|
|
9
|
+
- Read-only tools auto-run in every mode
|
|
10
|
+
- Approval tools (`write`, `edit`, `bash`) pause for approval in normal mode: `y` once, `a` always this session, `t` trust all write/edit/bash, `n` deny
|
|
11
|
+
- Every auto-approved call still renders its audit line
|
|
12
|
+
- `Tab` is the only mode switcher (cycles normal → yolo → plan → normal). `/yolo` and `/plan` are retired as typed commands — typing them explains this instead of switching. `/mode` prints the current mode
|
|
13
|
+
- Yolo mode never asks; plan mode is read-only (write/edit/bash blocked pre-execution with a replan note)
|
|
14
|
+
|
|
15
|
+
`/trust` is a session trust tier between normal and yolo:
|
|
16
|
+
|
|
17
|
+
- First `/trust` auto-approves all three approval tools at once without global yolo
|
|
18
|
+
- Status shows `+trust`
|
|
19
|
+
- Second `/trust` revokes
|
|
20
|
+
- In-memory only, never saved
|
|
21
|
+
|
|
22
|
+
## Scoped rules
|
|
23
|
+
|
|
24
|
+
Finer than all-or-nothing trust. Session-only, in-memory, never saved.
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
/allow <tool[:glob]>
|
|
28
|
+
/deny <tool[:glob]>
|
|
29
|
+
/rules
|
|
30
|
+
/rules clear
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
/allow bash:npm test*
|
|
37
|
+
/allow write:src/**
|
|
38
|
+
/deny bash:rm *
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
- Bare `/allow bash` matches any bash args. Bare tool name matches any call to that tool
|
|
42
|
+
- The glob matches the tool primary string, the same primary shown in the audit line: path for `read`/`write`/`edit`, pattern for `glob`/`grep`, command for `bash`, url/query for `webfetch`/`websearch`, taskId for `bash_output`, question for `ask_question`. Other tools have no primary, so only tool-only rules match them
|
|
43
|
+
- Glob dialect: `*` matches any sequence (including `/` and spaces), `?` matches exactly one char, everything else literal. Case-sensitive
|
|
44
|
+
- Tool names are single lowercase tokens. Anything else is rejected loudly rather than stored as a never-matching rule
|
|
45
|
+
|
|
46
|
+
## Precedence
|
|
47
|
+
|
|
48
|
+
One ordered rule lives in `src/policy.ts` (`decidePolicy` — tool request → policy → approval if needed → execution): deny wins over everything (including plan mode); plan mode passes approval-gated calls through to the execute gate, which refuses mutations with a replan note; then allow rules, yolo, session trust, always-allowed, and skill grants; otherwise the normal prompt flow applies. Covered by `tests/policy.test.ts`.
|
|
49
|
+
|
|
50
|
+
Skill-grant trust: only global (user-controlled `~/.claude|~/.agents`) skills arm turn-scoped grants. Project-local skill content is untrusted — it never silently escalates to `write`/`edit`/`bash` (sensitive names are reported, reads still auto-run). See [Skills](skills.md).
|
|
51
|
+
|
|
52
|
+
Rules only take effect on approval-gated calls (`write`/`edit`/`bash`) because read-only tools never consult approval. A rule naming another tool is accepted but inert.
|
|
53
|
+
|
|
54
|
+
Implementation is the pure module `src/permissions.ts`. Matching and parsing there have unit coverage in `tests/permissions.test.ts`.
|
|
55
|
+
|
|
56
|
+
## Denials
|
|
57
|
+
|
|
58
|
+
A denial returns the standard denial result and the model replans. Do not retry the denied call. Explain briefly and offer an alternative path.
|
|
59
|
+
|
|
60
|
+
## Security notes
|
|
61
|
+
|
|
62
|
+
- File tools reach anywhere on the machine. Treat sensitive locations as untrusted input
|
|
63
|
+
- Absolute symlinked paths show `link → target` in the activity line, so redirected reads/writes are visible
|
|
64
|
+
- Shell output passes through secret scrubbing: live provider-key env values are replaced with `[redacted]` before the model (or spill files) ever see them. Stored `auth.json` keys are not covered — never print session/auth files
|
|
65
|
+
- Never print full keys, never log them, never commit them. Masked display is last4 only
|
|
66
|
+
- Session files under `~/.atom/` can contain pasted secrets if typed as chat. Never print their contents, never commit them
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Providers and Models
|
|
2
|
+
|
|
3
|
+
8 remote providers plus 3 local runtimes (Ollama, LM Studio, llama.cpp) behind one UI (`src/providers.ts`). Manual-key only, mirroring opencode `/connect`. No OAuth, no browser flow. Kilo Gateway is the default provider and is OpenAI-compatible. Local runtimes need no key: they join the pickers once discovery reports models (loopback servers, auto-discovered).
|
|
4
|
+
|
|
5
|
+
## Provider table
|
|
6
|
+
|
|
7
|
+
| Provider | Key env (wins over stored) | Endpoint | Notes |
|
|
8
|
+
|---|---|---|---|
|
|
9
|
+
| kilo | `KILO_API_KEY` (optional — free models work anonymously) | `https://api.kilo.ai/api/gateway/chat/completions` | Kilo Gateway, OpenAI-compatible. Default provider. Anonymous access covers eligible free (`:free`) models; a key unlocks the full catalog |
|
|
10
|
+
| opencode-zen | `OPENCODE_ZEN_API_KEY` | `https://opencode.ai/zen/v1/chat/completions` | OpenAI-compatible chat/completions. Key at `https://opencode.ai/auth` |
|
|
11
|
+
| openai | `OPENAI_API_KEY` | `https://api.openai.com/v1/chat/completions` | OpenAI-compatible. Key at `https://platform.openai.com/api-keys` |
|
|
12
|
+
| anthropic | `ANTHROPIC_API_KEY` | `https://api.anthropic.com/v1/messages` | Messages API (`x-api-key` plus `anthropic-version: 2023-06-01`, `max_tokens` 4096). Key at `https://console.anthropic.com/settings/keys` |
|
|
13
|
+
| deepseek | `DEEPSEEK_API_KEY` | `https://api.deepseek.com/chat/completions` | OpenAI-compatible, no `/v1` prefix. Key at `https://platform.deepseek.com/api_keys` |
|
|
14
|
+
| mistral | `MISTRAL_API_KEY` | `https://api.mistral.ai/v1/chat/completions` | OpenAI-compatible. Key at `https://console.mistral.ai/api-keys` |
|
|
15
|
+
| google-gemini | `GEMINI_API_KEY` (alias `GOOGLE_API_KEY`) | `https://generativelanguage.googleapis.com/v1beta/models/{model}:streamGenerateContent?alt=sse` (`:generateContent` fallback) | `x-goog-api-key`. Key at `https://aistudio.google.com/apikey` |
|
|
16
|
+
| openai-compatible | stored key only | stored baseURL (`/chat/completions` appended when missing) | Prompts for baseURL (must be http/https). Live `/models` is authoritative |
|
|
17
|
+
|
|
18
|
+
Keys are never printed full (masked as last4), never logged, never in fixtures (tests use `"test-key"`).
|
|
19
|
+
|
|
20
|
+
## Kilo Gateway (default)
|
|
21
|
+
|
|
22
|
+
- Kilo is ATOM's default provider: fresh installs start on Kilo with no key required
|
|
23
|
+
- The model catalog is discovered live via `GET https://api.kilo.ai/api/gateway/models` (cached for 5 minutes; `/models refresh` re-fetches while Kilo is active). Nothing is hardcoded — the catalog is authoritative, and free-model availability can change as Kilo updates it
|
|
24
|
+
- Anonymous access covers eligible free models (ids ending in `:free`, including the `kilo-auto/free` dynamic routing model, which Kilo resolves server-side). Without a key ATOM prefers `kilo-auto/free` when exposed, else the first free model, else the first live id
|
|
25
|
+
- Configure a key with `/provider` (validated, stored in `~/.atom/auth.json`) or `KILO_API_KEY` to unlock the full catalog; authenticated requests send `Authorization: Bearer <key>`, anonymous requests send no auth header at all
|
|
26
|
+
- Free models show a `(free)` badge in `/model` and match the `free` filter
|
|
27
|
+
- Chat is OpenAI-compatible (`POST /chat/completions`) with streaming, tool calls, and usage metadata on the shared OpenAI-chat path; failures surface as short actionable messages (e.g. `Kilo: anonymous free-model rate limit reached.`, `Kilo: API key is invalid.`, `Kilo: model is unavailable.`, `Kilo: gateway temporarily unavailable.`)
|
|
28
|
+
|
|
29
|
+
## Defaults and fallbacks
|
|
30
|
+
|
|
31
|
+
- Default provider: `kilo` (default model: `kilo-auto/free` when no key is configured)
|
|
32
|
+
- Zen default model: `deepseek-v4-pro`, picked for reliable multi-step tool use. Free models (`big-pickle` and similar) stay selectable via `/model` for quick single-turn questions. Override any time with `/model` or `OPENCODE_ZEN_MODEL`
|
|
33
|
+
- Each provider ships a fallback model list used when the live `/models` call fails. The live list is authoritative when reachable. Kilo's fallback is just the `kilo-auto/free` routing placeholder (one id, not a catalog)
|
|
34
|
+
|
|
35
|
+
Fallbacks are build-time curated (2026-09-07) from vendor docs. See the header comment in `src/providers.ts` for per-vendor sources.
|
|
36
|
+
|
|
37
|
+
## Key resolution
|
|
38
|
+
|
|
39
|
+
Precedence per provider (`src/auth.ts`):
|
|
40
|
+
|
|
41
|
+
1. First non-empty env var in the provider `envVars` list
|
|
42
|
+
2. Stored key in `~/.atom/auth.json`
|
|
43
|
+
|
|
44
|
+
`openai-compatible` has no env vars, so it is stored-only by construction. Stored zen key applies when no env key is set.
|
|
45
|
+
|
|
46
|
+
Auth file shape:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"version": 1,
|
|
51
|
+
"providers": {
|
|
52
|
+
"<id>": { "apiKey": "...", "baseURL?": "..." }
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
File lives at `~/.atom/auth.json` (`ATOM_HOME` overrides the home dir). `0600` on POSIX, best-effort on Windows. Missing or corrupt file loads as empty auth, never throws.
|
|
58
|
+
|
|
59
|
+
## Switching
|
|
60
|
+
|
|
61
|
+
- `/provider`: pick provider, paste key once (validated, stored), chat. Kilo's key is optional — without one the prompt offers anonymous free-model use. Switching provider keeps session history text. System prompt stays
|
|
62
|
+
- `/model`: unified picker — active provider's live models first (fallback on any failure), then every other keyed provider's models plus the always-visible keyless Kilo and local lists (cached live list when warm, else fallback). `openai-compatible` joins only with both a key and a stored baseURL. Type to filter (`free` matches free Kilo models), list windows to 10 rows, picking another provider's model switches provider too
|
|
63
|
+
- `/models refresh`: re-probes local servers; while Kilo is active it refreshes the Kilo gateway catalog instead
|
|
64
|
+
- `/effort`: reasoning-effort picker (`Auto`/`Low`/`Medium`/`High`/`Max`). Sent for every model on every provider: `reasoning_effort` on OpenAI-chat kinds (zen, OpenAI, DeepSeek, Mistral, Kilo, openai-compatible, locals), a `thinking` budget on Anthropic, a `thinkingConfig.thinkingLevel` on Gemini. `Auto` omits the knob. A model that truly lacks the knob fails the POST with a 400 naming it — the turn warns and retries once without it, so `(unsupported)` only ever reflects an actual server rejection
|
|
65
|
+
|
|
66
|
+
Custom server: pick `openai-compatible`, paste the baseURL (validated as http/https, trailing slashes trimmed) and key. Endpoint helper appends `/chat/completions` when missing.
|
|
67
|
+
|
|
68
|
+
See [Configuration](configuration.md) for env var details and [Troubleshooting](troubleshooting.md) for auth failures.
|
|
69
|
+
|
|
70
|
+
## Prompt caching
|
|
71
|
+
|
|
72
|
+
ATOM constructs a cache-friendly prompt on every POST and each provider realizes it its own way (`ProviderDef.cache` in `src/providers.ts`, assembled in `src/prompt-cache.ts`). No prompt caching is implemented harness-side — this is deliberate prefix construction plus usage reporting.
|
|
73
|
+
|
|
74
|
+
- **Stable prefix** (byte-identical across POSTs): system instructions + project overlay + tool definitions. The per-turn env block (timestamps, git status) splits off into its own trailing system content, so it never breaks the prefix. No timestamps, random IDs, or dynamic content in the prefix; tool order is source order.
|
|
75
|
+
- **Anthropic**: explicit `cache_control: {type: ephemeral}` breakpoints on the stable system block and the last tool (5m default TTL, no beta header). System renders as blocks only when an env tail splits off, else the legacy string.
|
|
76
|
+
- **OpenAI-shape** (kilo/zen/openai/deepseek/mistral/compatible): consecutive `[stable, dynamic]` system messages (content-neutral concatenation); prefix caching itself is automatic server-side.
|
|
77
|
+
- **Gemini**: `system_instruction` splits into stable/dynamic parts the same way.
|
|
78
|
+
- **Hits are only ever shown when reported**: Anthropic `cache_read/_creation_input_tokens`, OpenAI `prompt_tokens_details.cached_tokens`, DeepSeek `prompt_cache_hit_tokens`, Gemini `cachedContentTokenCount` accumulate into session totals and surface in `/context`. Absent fields display as "(not reported)", never zeros.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Sessions
|
|
2
|
+
|
|
3
|
+
Kill-safe persistence. A killed, crashed, or failed session keeps the last good save.
|
|
4
|
+
|
|
5
|
+
## File and shape
|
|
6
|
+
|
|
7
|
+
Path: `~/.atom/session.json` (`ATOM_HOME` overrides home). `0600` on POSIX, best-effort on Windows. Lives outside the repo, never commit it.
|
|
8
|
+
|
|
9
|
+
Shape (`src/session.ts`):
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
{version:1, savedAt, provider, model, effort, mode, usageTotals, history, turns, goal?}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
- `history`: full API history including system plus tool pairs
|
|
16
|
+
- `turns`: display transcript
|
|
17
|
+
- `goal`: live session goal snapshot (`{objective, active, stats}`) when one is pinned; restored verbatim by `/resume` and session switches (see [Goals](goals.md)). Corrupt or absent goal data loads as no goal
|
|
18
|
+
- Writes are atomic (temp file plus rename) to survive kills mid-write
|
|
19
|
+
- Loads never throw: missing file is `missing`, anything malformed is `corrupt`. Caller shows a one-line notice and starts fresh
|
|
20
|
+
|
|
21
|
+
Privacy: the file can contain pasted secrets if typed as chat. Never print its contents.
|
|
22
|
+
|
|
23
|
+
## Save policy
|
|
24
|
+
|
|
25
|
+
Every completed turn and clean exit writes the file. Failed or cancelled turns roll back and never touch the file, so a bad turn cannot corrupt the last good save. Disk errors propagate to the caller, which ignores them; the in-memory session still applies.
|
|
26
|
+
|
|
27
|
+
## Rollback semantics (three scopes, not a transaction)
|
|
28
|
+
|
|
29
|
+
ATOM is a coding agent, not a database. Cancelling or failing a turn rolls the conversation back while the world it touched stays as it is. The contract lives in `src/rollback.ts` and is covered by `tests/rollback.test.ts`:
|
|
30
|
+
|
|
31
|
+
| Scope | Mode | Meaning |
|
|
32
|
+
|---|---|---|
|
|
33
|
+
| Conversation | Automatic | History/turns splice back to the turn start on cancel or POST failure; to a checkpoint mark on `/rewind` conversation scopes; to empty on `/clear`. Rolled-back turns never reach `session.json`. Tool *error results* are model-visible results and are never rolled back — only whole-turn cancel/failure |
|
|
34
|
+
| Filesystem | Explicit only | The sole filesystem rollback is an explicit `/rewind` restore (byte-exact, hash-verified — see below). Cancel and failure never revert disk. The cancelled-turn line says so outright: `(cancelled) conversation rolled back; files and processes were NOT reverted` |
|
|
35
|
+
| Process | Never | Foreground `bash` runs to completion (cancel stops the turn *after* the current tool finishes, never mid-exec). Background tasks are detached and survive cancel; nothing is ever killed. Shell side effects are not snapshotted and cannot be undone |
|
|
36
|
+
|
|
37
|
+
No global transactional shell execution exists by design — shell commands are outside every rollback scope.
|
|
38
|
+
|
|
39
|
+
Lineage rule: file checkpoints are bound to the history lineage they were captured in. `/clear`, `/new`, `/resume`, and compaction replace the history array and therefore discard all checkpoints (with a one-line notice when non-empty). Disk files are unaffected — only the undo evidence goes. Checkpoints are also never persisted: they are in-memory and do not survive a restart, so `/resume` in a fresh process starts with an empty picker.
|
|
40
|
+
|
|
41
|
+
Snapshot lifecycle notes:
|
|
42
|
+
|
|
43
|
+
- Creation: `write`/`edit` capture prior bytes *before* mutating, silently, with no prompt or config. Validation rejections (bad path, stale read, no match) return before capturing, so refused calls leave no checkpoint. A capture failure never fails the mutation it precedes — unless safety policy explicitly requires otherwise, which no current policy does.
|
|
44
|
+
- Restore: `restoreCheckpointFiles` verifies the snapshot hash *before* writing and re-verifies after; any failure is a loud `Error:` string and leaves the live file untouched. Restores refresh (or forget, on deletion) the stale-read fingerprint so the next `edit` does not false-refuse.
|
|
45
|
+
- Cleanup: at most 50 checkpoints (oldest evicted first); large-file temp copies are removed on eviction and on lineage drops; crash-leftover temp copies older than 24h are pruned on every new spill. In-memory buffers are bounded by the checkpoint cap.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | Effect |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `/resume` | Restore the last saved session: turns, history, settings, usage, plus the live goal (text, state, cumulative stats) when one was saved. Re-surfaces the `Touched files:` lists stored in compacted summaries (same stored format), so the continued session knows what was touched without re-exploring the tree |
|
|
52
|
+
| `/clear` | Clear conversation history and end the live goal with a notice. Keeps session token totals |
|
|
53
|
+
| `/new` | Start a brand-new session (conversation plus counters reset, previous kept for `/resume`; the checklist restarts too; the live goal ends) |
|
|
54
|
+
| `/rename <name>` | Rename the current session only (id, `createdAt`, and history untouched; quotes optional: `/rename "name with spaces"`; bare `/rename` prints usage) |
|
|
55
|
+
| `/session [filter]` | Interactive session switcher: most-recent-first picker with fuzzy filter, `(current)` marker, turn counts, and relative ages. `Enter` switches, `Esc` cancels with the live session untouched |
|
|
56
|
+
| `/rewind` | Restore files to a session checkpoint. Files only; shell side effects are never snapshotted |
|
|
57
|
+
|
|
58
|
+
## Multiple persistent sessions (`src/sessions.ts`)
|
|
59
|
+
|
|
60
|
+
Every conversation automatically belongs to a durable session. One JSON record per session under `~/.atom/sessions/<id>.json` (`ATOM_HOME` overrides home), plus a plaintext `active` pointer holding the active session id. Same atomic-write and permission posture as `session.json` (temp file plus rename, `0600` POSIX best-effort); loads never throw (missing or malformed files read as absent and are skipped in listings). Provider/model-agnostic: the store never imports LLM clients.
|
|
61
|
+
|
|
62
|
+
Record shape:
|
|
63
|
+
|
|
64
|
+
```text
|
|
65
|
+
{id, title, createdAt, updatedAt, cwd, provider, model, effort, mode,
|
|
66
|
+
usageTotals, history, turns, goal?, metadata}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
- `id`: stable `ses_` identifier, never derived from the display name
|
|
70
|
+
- `title`: mutable display name. Fresh sessions default to the exact local creation date and time (`September 9, 2026 20:41:32`); `createdAt` stays a separate machine-readable ISO timestamp either way
|
|
71
|
+
- `updatedAt`: bumps on every meaningful mutation (completed turn, compaction, rename, settings/history write). Switching sessions is navigation, not a mutation, and never bumps it
|
|
72
|
+
- `history`/`turns`: the full conversation state, so reopening a session restores it exactly. No transient UI state is stored (scroll, cursor, pickers, queue never persist)
|
|
73
|
+
|
|
74
|
+
Session API (`createSession`, `getSession`, `listSessions`, `updateSession`, `renameSession`, `deleteSession`, `loadSession`, `saveSession`, `setActiveSession`, `getActiveSession`, plus `touchSession` and `ensureActiveSession`): listings sort most-recently-updated first, renames reject empty names without touching the record, and explicit ids that collide fall back to a fresh id instead of overwriting.
|
|
75
|
+
|
|
76
|
+
Switch semantics (see `switchToSession` in `src/App.tsx`): the outgoing live turns snapshot into their own record first (skipped when the live view holds no turns, so a fresh mount can never wipe a record); the target's history/turns then *replace* the live arrays wholesale — never merged, never duplicated — with provider/model/effort/mode, usage, title, and the legacy `session.json` mirror following the switch. In-memory lineage drops (file checkpoints, like `/resume` and `/new`) and the per-conversation TODO checklist resets (same as `/new`); scoped allow/deny rules, trust, and always-approvals are user settings and survive the switch, also like `/new`. A missing or unreadable target errors without touching the live session, and re-picking the current session is a no-op (reloading from disk would drop unpersisted live turns).
|
|
77
|
+
|
|
78
|
+
Restart behavior: records and the active pointer survive; the conversation itself never auto-restores (same philosophy as `/resume`) — pick the session in `/session` to continue exactly where it left off.
|
|
79
|
+
|
|
80
|
+
## Model memory across restarts
|
|
81
|
+
|
|
82
|
+
Your `/model`, `/provider`, and `/effort` picks persist automatically: every completed turn and clean exit saves them, and the next launch restores provider, model, and effort (plus the resolved key/endpoint) with a fresh conversation. The transcript itself only ever restores via an explicit `/resume`. Explicit config wins: `OPENCODE_ZEN_MODEL` beats the saved model when set. A saved provider whose key no longer resolves (revoked env/stored key) falls back to the Kilo default instead of stranding startup — except a saved Kilo session, which restores keyless on anonymous free models. A plain restart starts fresh otherwise (normal mode, empty counters); `/resume` additionally restores the saved mode and usage totals. Committed write/edit diff previews are display-only and stripped on save, so resumed transcripts are label-only.
|
|
83
|
+
|
|
84
|
+
`/rewind` details:
|
|
85
|
+
|
|
86
|
+
- Every `write`/`edit` takes a silent pre-mutation snapshot (`src/snapshots.ts`, executed in `src/tools/filesystem.ts`)
|
|
87
|
+
- Restore writes bytes behind the executors and refreshes (or forgets, on deletion) the stale-read fingerprint, so the next `edit` does not false-refuse
|
|
88
|
+
- Scope is file bytes only. Commands already run, packages installed, or external state changed by `bash` are not undone
|
|
89
|
+
|
|
90
|
+
## Token totals
|
|
91
|
+
|
|
92
|
+
`/clear` and compaction keep cumulative spend. The footer `NK` total keeps growing after compaction; the `P%` load tracks current context only. See [Compaction](compaction.md).
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Skills
|
|
2
|
+
|
|
3
|
+
Claude-Code-style `SKILL.md` adoption. Discovery, listing, invocation, and turn-scoped tool grants build on one registry (`src/skills.ts`).
|
|
4
|
+
## Where skills live
|
|
5
|
+
|
|
6
|
+
Two levels, four roots:
|
|
7
|
+
|
|
8
|
+
- Project: `<projectDir>/.claude/skills/<name>/SKILL.md`
|
|
9
|
+
- Project: `<projectDir>/.agents/skills/<name>/SKILL.md` (where `skills.sh` installs)
|
|
10
|
+
- Global: `~/.claude/skills/<name>/SKILL.md` (`$HOME` via OS homedir)
|
|
11
|
+
- Global: `~/.agents/skills/<name>/SKILL.md` (`skills.sh` global installs)
|
|
12
|
+
|
|
13
|
+
## SkillRegistry (cached metadata)
|
|
14
|
+
|
|
15
|
+
The TUI reads skills through `createSkillRegistry` (`src/skills.ts`), not raw rescans: parsed Tier-1 metadata (name, description, invocation flags, `allowed-tools`) is cached in memory per App instance. Each `refresh()` revalidates with one `stat` (mtime + size) per `SKILL.md` and re-reads only added or modified entries — per-message cost drops from ~1MB of file reads to a directory listing plus stats. A missing/unreadable file, a fixed file, or a deleted skill takes effect on the very next refresh (no restart, never stale); vanished roots drop silently. Bodies and `references/` stay lazy (`loadSkillBody`, on activation only). The uncached `discoverSkills` remains for one-shot/headless use with byte-identical parsing.
|
|
16
|
+
|
|
17
|
+
A missing skills directory is normal and silent. Per-skill failures surface as warning strings, never throws.
|
|
18
|
+
|
|
19
|
+
## SKILL.md contract
|
|
20
|
+
|
|
21
|
+
Frontmatter fields (single-line `key: value`, folded and literal continuations supported):
|
|
22
|
+
|
|
23
|
+
| Field | Meaning |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `name` | Skill name. Defaults to the directory name when absent |
|
|
26
|
+
| `description` | Trigger description. Defaults to the first non-heading paragraph when absent. Required: empty body plus no description means skipped |
|
|
27
|
+
| `user-invocable` | `false` hides the skill from manual `/name` invocation. Default `true` |
|
|
28
|
+
| `disable-model-invocation` | `true` excludes the skill from auto-match. Default `false` |
|
|
29
|
+
| `allowed-tools` | Space and/or comma-separated tool names. Lowercased, deduped. Turn-scoped auto-approvals on invoke — global skills only (see trust note below). Unknown names match nothing |
|
|
30
|
+
|
|
31
|
+
Boolean forms accepted: `true`/`false`, `yes`/`no`, `on`/`off`, `1`/`0`, any case. Unrecognized values fall back to defaults.
|
|
32
|
+
|
|
33
|
+
Support files: `references/<...>` and `scripts/<...>` mentions inside the body are inlined on demand (first 3 unique mentions, 8KB each, traversal outside the skill dir dropped, missing files skipped silently).
|
|
34
|
+
|
|
35
|
+
## Listing and precedence
|
|
36
|
+
|
|
37
|
+
`/skills` opens the searchable picker (names only, type to filter, arrows to browse, `Enter` stages for confirm). `skillsListText` (headless use) prints `Skills (N):` with one runnable `/skill:name` plus source per line — no descriptions in either surface. Model-only skills show `[auto-only]` instead of hiding. Notes and warnings ride along visibly. Empty with no warnings prints the install hint (`add SKILL.md skills under .claude/skills/, .agents/skills/, or the ~/. counterparts`).
|
|
38
|
+
|
|
39
|
+
Name clashes: global (personal) wins over project on exact-name matches, with a visible note. Same-level duplicates keep the first with a note. Pure function `resolveSkills`, covered by `tests/skills.test.ts`.
|
|
40
|
+
|
|
41
|
+
## Invocation (progressive disclosure, Claude-Code-style)
|
|
42
|
+
|
|
43
|
+
Three tiers: (1) name plus description of every skill is known to the matcher at all times; (2) the `SKILL.md` body loads only on activation; (3) `references/` and `scripts/` files load on demand (inlined for explicit manual loads; the model reads them via `read` for auto loads). The transcript always shows one plain line per load (`deploy loaded`), never the body.
|
|
44
|
+
|
|
45
|
+
- Manual: `/skill:name` (canonical; legacy `/skill-name` still works) loads the full body plus inlined references into context for that turn. Discovery without dispatch: the `/skills` picker (type to filter, arrows to browse, `Enter` stages `/skill:name` into the input — nothing is sent) and the `/` slash menu (skill rows complete on first `Enter`, run on the second) both confirm before loading; a fully typed `/skill:name` or `/skill-name` runs on first `Enter`. `allowed-tools` in frontmatter become turn-scoped auto-approvals. User-invocable `false` entries reject manual invocation
|
|
46
|
+
- Auto: deterministic whole-word description match with a high bar — distinct `name` plus `description` words (length 3 or more, stopwords dropped) appearing as whole message words, at least 3 hits, best score first, at most 1 skill per turn. Auto loads Tier 2 only (body without inlined references, truncated at 12KB with a read pointer). `disable-model-invocation` skills never match. See `matchSkills` in `src/skills.ts`
|
|
47
|
+
- Deny rules still win over skill grants. See [Permissions](permissions.md)
|
|
48
|
+
|
|
49
|
+
Grant trust (`skillGrantsFor` in `src/policy.ts`, covered by `tests/policy.test.ts`): global skills live under your own `~/.claude|~/.agents`, so their `allowed-tools` arm turn-scoped auto-approvals as before. Project skills live in repo content — possibly a cloned repo you have never audited — so they never arm grants: sensitive names (`write`/`edit`/`bash`) are announced as still-needing-approval, everything else behaves identically (read-only tools auto-run either way, nothing functional is lost).
|
|
50
|
+
|
|
51
|
+
## Authoring checklist
|
|
52
|
+
|
|
53
|
+
1. Create `.claude/skills/<name>/SKILL.md` (or `.agents/skills/<name>/SKILL.md`) with `name` and `description`
|
|
54
|
+
2. Keep the body self-contained; reference large helpers via `references/...` so they inline only when needed
|
|
55
|
+
3. Declare least-privilege `allowed-tools`
|
|
56
|
+
4. Set `user-invocable: false` for auto-only helpers, `disable-model-invocation: true` for manual-only helpers
|
|
57
|
+
5. Verify with `/skills` listing plus `tests/skills*.test.ts` patterns
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Tools
|
|
2
|
+
|
|
3
|
+
13 local tool executors (`src/tools.ts`). Node builtins plus global fetch only. Every executor returns a string and never throws across the tool boundary: failures come back as `Error: ...` strings so the model can react.
|
|
4
|
+
|
|
5
|
+
Source of truth for names and shapes is `TOOL_DEFINITIONS` in `src/tools/registry.ts` (re-exported through the `src/tools.ts` barrel). The validator and loop build their `Available: ...` lists from it.
|
|
6
|
+
|
|
7
|
+
## Extension tools
|
|
8
|
+
|
|
9
|
+
Extensions can register brand-new model-callable tools via `api.registerTool({ name, description, parameters, execute, requireApproval? })` (`src/extensions.ts`, store in `src/tools/custom.ts`). From the model's perspective they behave exactly like builtins: they appear in the tool definitions sent on every chat POST (`allToolDefinitions()`, including the Anthropic/Gemini adapters), validate args inline (`Error: invalid call: ...`, never runs on bad args), dispatch through the shared loop with identical cancellation semantics, and a throwing implementation degrades to an `Error:` result string. They carry no scheduler effect metadata, so they always execute as serial singletons. Approval default is fail-closed: custom tools require approval unless the registration opts out with `requireApproval: false` (reserved for pure side-effect-free helpers). Names must match `[A-Za-z0-9_-]{1,64}` and must not collide with builtins or each other — violations throw loudly at registration.
|
|
10
|
+
|
|
11
|
+
## The 13 tools
|
|
12
|
+
|
|
13
|
+
| Tool | What it does | Permission in normal mode |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| `read` | Read files, list directories. Args: `path`, optional 1-based `offset`/`limit` | auto |
|
|
16
|
+
| `write` | Create or overwrite files (creates parent dirs). Silent pre-mutation snapshot for rewind | asks |
|
|
17
|
+
| `edit` | Exact-match patch. Fails on no match, on multiple matches without `replaceAll`, on stale read | asks |
|
|
18
|
+
| `grep` | Line-regex search under `dir`. `include` glob, `outputMode`: `content`, `files_with_matches`, `count` | auto |
|
|
19
|
+
| `glob` | List paths matching pattern under `dir`, newest-first | auto |
|
|
20
|
+
| `bash` | Shell command. JSON result with `exitCode`, `stdout`, `stderr`. Optional `runInBackground` | asks |
|
|
21
|
+
| `bash_output` | Poll a background shell task by `taskId` | auto |
|
|
22
|
+
| `webfetch` | Fetch a page as `markdown`, `text`, or `html`. http upgrades to https. Gated by the network SSRF policy (see below) | auto |
|
|
23
|
+
| `websearch` | Keyless discovery via DuckDuckGo HTML endpoint. `query`, optional `numResults`, `site` | auto |
|
|
24
|
+
| `ask_question` | Interactive picker for clarifications. Needs `question` plus at least 2 `options` | n/a (is interaction) |
|
|
25
|
+
| `todowrite` | Replace the session task checklist | auto |
|
|
26
|
+
| `todo_get` | Read the session task checklist | auto |
|
|
27
|
+
| `todo_update` | Update one checklist item by index | auto |
|
|
28
|
+
|
|
29
|
+
Read-only set: `read`, `grep`, `glob`, `webfetch`, `websearch`, `bash_output`, `todowrite`, `todo_get`, `todo_update`. Approval set: `write`, `edit`, `bash`. `ask_question` never needs approval because it is user interaction.
|
|
30
|
+
|
|
31
|
+
## Caps and truncation
|
|
32
|
+
|
|
33
|
+
| Path | Cap | Behavior |
|
|
34
|
+
|---|---|---|
|
|
35
|
+
| `read` output | ~64KB | Head plus truncation note. Full text spills to `<tmpdir>/atom-overflow/` with a `read` pointer |
|
|
36
|
+
| `bash` stdout/stderr | ~8KB each | Each stream capped independently, JSON flags `stdoutTruncated`/`stderrTruncated`, overflow pointer on spill |
|
|
37
|
+
| `bash_output` streams | ~8KB each | Same spill behavior for background stdout/stderr |
|
|
38
|
+
| `webfetch` download | ~1MB | Noted as `[truncated: download exceeded ~1MB]` |
|
|
39
|
+
| `webfetch` output | ~64KB | Same overflow-file pointer as `read` |
|
|
40
|
+
| `grep` content | 100 matches | Lines over 200 chars shortened with an ellipsis. `file:line: text` shape |
|
|
41
|
+
| `grep` files/count | 100 files | `files_with_matches` is newest-first with a `Found N file(s)` header. `count` adds per-file `file:count` plus totals covering every match |
|
|
42
|
+
| `glob` | 200 matches | Newest-first by mtime, recency as relevance proxy |
|
|
43
|
+
| `websearch` | default 8, max 20 | Numbered title plus url plus snippet blocks, or `No results.` Query capped at 500 chars |
|
|
44
|
+
| `bash` timeout | default 60000ms, max 120000ms | `timedOut` flag in the JSON result |
|
|
45
|
+
| `bash_output` wait | default 5000ms, max 60000ms | Polls about every 100ms until exit or wait expiry |
|
|
46
|
+
| Background tasks | 20 records | Oldest evicted first, temp files pruned best-effort |
|
|
47
|
+
| Agentic loop | uncapped by default | optional cap via `ATOM_MAX_TOOL_STEPS`, clamped 5-100 |
|
|
48
|
+
| Parallel writes | per-file keys (symlink-aware) | disjoint files batch, same file strictly ordered |
|
|
49
|
+
|
|
50
|
+
Count-cap notes (`grep`/`glob` over-cap) and prompt-assembly caps (skills, compact, AGENTS.md, history) do not spill: re-query to narrow.
|
|
51
|
+
|
|
52
|
+
## Path handling
|
|
53
|
+
|
|
54
|
+
No path sandbox. Relative paths resolve against cwd. Absolute paths and `..` escapes are allowed anywhere on the machine, including sensitive locations like `~/.ssh/`. Treat those contents as untrusted. Never exfiltrate or commit secrets. The permission mode is the control plane. See [Permissions](permissions.md).
|
|
55
|
+
|
|
56
|
+
## Network policy (`webfetch` SSRF gate)
|
|
57
|
+
|
|
58
|
+
Every URL — the initial one and every redirect hop — classifies into a zone (`public`, `localhost`, `private` RFC1918/CGNAT/TEST-NET, `link-local` incl. cloud metadata `169.254.169.254`, `blocked` for unparseable/unresolvable) and is checked against the `network` policy from `atom.json` (defaults: public + localhost allowed). Redirects are followed manually (cap 5, loop-detected, credentialed/scheme-changing targets refused) so a public URL can never bounce to metadata or the LAN unseen; each hop re-resolves DNS and the worst zone wins for multi-address hosts. IP-literal tricks (octal/hex forms, IPv4-mapped IPv6) classify by their real address. Known limitation: DNS rebind between check and fetch (TOCTOU) would need connection-level IP pinning, which global fetch does not offer. See [Configuration](configuration.md). Covered by `tests/policy.test.ts`.
|
|
59
|
+
|
|
60
|
+
Only empty/non-string paths and null bytes are rejected.
|
|
61
|
+
|
|
62
|
+
## Safety guards
|
|
63
|
+
|
|
64
|
+
- Read-tracking: `read` records a sha1 per resolved file. `edit` refuses with a stale-read error when the file changed since the last read. `write`/`edit` refresh the record. Files never read this session have no record.
|
|
65
|
+
- Pre-mutation snapshots: `write`/`edit` capture prior bytes for `/rewind`. See [Sessions](sessions.md).
|
|
66
|
+
- Validation: malformed args return `Error: invalid call: ... Fix the arguments and retry.` The tool never ran. Runtime failures return plain `Error: ...`.
|
|
67
|
+
- Binary skip: `grep` skips unreadable files and files containing null bytes.
|
|
68
|
+
|
|
69
|
+
## Task checklist invariants (runtime-enforced)
|
|
70
|
+
|
|
71
|
+
Beyond prompt guidance, the harness refuses impossible states as invalid calls (list untouched):
|
|
72
|
+
|
|
73
|
+
- At most one `in_progress` item — complete or pause the current one first (both `todowrite` rewrites and `todo_update` patches).
|
|
74
|
+
- A `todowrite` rewrite never silently reopens a completed item; reopening is an explicit `todo_update` status patch.
|
|
75
|
+
- Completing the last open item clears the list.
|
|
76
|
+
|
|
77
|
+
## Verification gate (runtime-enforced)
|
|
78
|
+
|
|
79
|
+
The system prompt forbids unverified finishes, and the loop enforces it: after a successful `write`/`edit` to a **code path** (source extensions — docs, configs, and data never arm the gate), final text does not end the turn until a verification command **passes** (`exitCode: 0`; a failing run keeps the gate armed so the model fixes forward). Instead the turn continues with a verification follow-up, up to 3 nag rounds or the step budget — then it ends labeled: `(blocked: …)` on spent budget, `(unverified: …)` naming the files and the reason otherwise. Cancellation always wins immediately; Ctrl+C/Esc behavior is unchanged.
|
|
80
|
+
|
|
81
|
+
## Background shell
|
|
82
|
+
|
|
83
|
+
`bash` with `runInBackground: true` returns immediately with `{backgroundTaskId, status: "running", hint}`. Poll with `bash_output` (`taskId`, optional `timeoutMs`). Result JSON carries `taskId`, `running`, `exitCode` (null while running), `stdout`, `stderr`, `timedOut`.
|
|
84
|
+
|
|
85
|
+
## Scheduling (effect-aware parallelism)
|
|
86
|
+
|
|
87
|
+
One assistant message's `tool_calls` run under a conservative scheduler (`src/scheduler.ts`, planned by `planToolBatches` in `src/zen.ts` — a thin wrapper over `planBatches` — executed by the shared loop core in `src/agent/loop.ts`):
|
|
88
|
+
|
|
89
|
+
- Each tool declares effects — `filesystem: none | read | write`, `network: none | read | write`, `process: none | spawn`, plus `interactive`, `exclusive` (shared ambient state), and `deterministic`. Missing metadata fails safe to serial.
|
|
90
|
+
- Batchable reads (`read`/`grep`/`glob` over files, `webfetch`/`websearch` over network, `bash_output` per task) always run concurrently — parallel by default, same target included, since pure reads can never race each other.
|
|
91
|
+
- Writes batch on disjoint canonical files and run concurrently; same-file mutations, read/write pairs on the same file, process spawns, interactive prompts, and todo-state tools are always serial singletons so program order holds. Target-scoped write batching across *related* paths (e.g. a directory scan racing a write inside it) is a deliberate non-goal.
|
|
92
|
+
- Results commit in original call order (one transcript entry per call); cancel stops between batches and a mid-batch throw aborts with no partial commits; approval still happens per call. Covered by `tests/scheduler.test.ts` (planning) and `tests/parallel-calls.test.ts` (ordering, timing, serial pins).
|
|
93
|
+
|
|
94
|
+
Windows note: background spawns attached (detached children drop output on Windows). POSIX uses detached process groups. Observable contract is the same: immediate return, independent run, output to temp files under the OS temp dir.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
What to check first, in order. No guessing: verify with the command or file cited.
|
|
4
|
+
|
|
5
|
+
## No key / auth errors
|
|
6
|
+
|
|
7
|
+
Symptom: chat errors inline with a `/provider` pointer, or provider HTTP 401.
|
|
8
|
+
|
|
9
|
+
1. Check env wins over stored: `KILO_API_KEY` (optional — Kilo free models work without it), `OPENCODE_ZEN_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `DEEPSEEK_API_KEY`, `MISTRAL_API_KEY`, `GEMINI_API_KEY` (or `GOOGLE_API_KEY`). Kilo failures print short messages: anonymous 429 means the free-model limit (`Kilo: anonymous free-model rate limit reached.`), 401 means a bad Kilo key, 404 means the model left the catalog (pick another via `/model`)
|
|
10
|
+
2. Run `/provider`, repaste the key. Validated before storage in `~/.atom/auth.json` (`0600` POSIX)
|
|
11
|
+
3. Keys display masked (last4 only). If you see `(no key)`, nothing resolved for that provider
|
|
12
|
+
4. `openai-compatible` is stored-only. Confirm both stored key and stored baseURL (must be http/https)
|
|
13
|
+
|
|
14
|
+
Never print full keys, never commit them, never put them in fixtures.
|
|
15
|
+
|
|
16
|
+
## Model list fails
|
|
17
|
+
|
|
18
|
+
`/model` falls back to the offline list when the live `/models` call fails (Kilo falls back to the `kilo-auto/free` routing placeholder). That is expected offline. Check endpoint override (`OPENCODE_ZEN_ENDPOINT`), network, and key validity before assuming a bug. While Kilo is active, `/models refresh` re-fetches the gateway catalog.
|
|
19
|
+
|
|
20
|
+
Effort (`/effort`: `Auto`/`Low`/`Medium`/`High`/`Max`) is sent for every model on every provider. If a turn warns that an effort level "is not supported by" a model, the server rejected the knob with a 400 and the turn continued without it — the setting is kept, so switching back to a supporting model re-applies it.
|
|
21
|
+
|
|
22
|
+
## Tool approval confusion
|
|
23
|
+
|
|
24
|
+
- `/mode` prints the current mode. `Tab` cycles normal → yolo → plan → normal (`/yolo` and `/plan` are retired as typed commands)
|
|
25
|
+
- `/trust` toggles session trust (`+trust` in status). Again revokes
|
|
26
|
+
- `/rules` lists allow/deny rules. Deny wins over trust, yolo, always, and skill grants
|
|
27
|
+
- A denial returns the standard denial result. Do not retry the same call; replan
|
|
28
|
+
- `read` before `edit`: `edit` refuses with a stale-read error when the file changed since the last read. Read again, then edit
|
|
29
|
+
|
|
30
|
+
See [Permissions](permissions.md) and [Tools](tools.md).
|
|
31
|
+
|
|
32
|
+
## Session and compact issues
|
|
33
|
+
|
|
34
|
+
- `/resume` reports missing or corrupt: `~/.atom/session.json` is absent or malformed. Caller starts fresh with a one-line notice. Only completed turns save, so a failed turn never clobbers the last good save
|
|
35
|
+
- Compact failures suggest `/clear`. Overflow retries once after dropping the oldest half of user-turns. Thrash guard disables auto-compact after 3 auto-compactions without the load dropping below threshold
|
|
36
|
+
- `token: n/a` means no usage reported yet. Not an error
|
|
37
|
+
- Bare `token: NK` means the model has no verified window in `src/context-windows.ts`. Not an error
|
|
38
|
+
|
|
39
|
+
See [Sessions](sessions.md) and [Compaction](compaction.md).
|
|
40
|
+
|
|
41
|
+
## Web tools blocked
|
|
42
|
+
|
|
43
|
+
- `websearch` may return `Error: websearch blocked by DuckDuckGo bot protection (HTTP 403; best-effort search — retry later)`. Retry later; the endpoint is best-effort
|
|
44
|
+
- `webfetch` upgrades http to https (noted), allows only http/https, caps downloads at about 1MB and output at about 64KB. Large pages spill to an overflow file with a `read` pointer
|
|
45
|
+
|
|
46
|
+
## TUI does not start
|
|
47
|
+
|
|
48
|
+
- `npm start` needs a TTY. Run in an interactive terminal, not a piped script
|
|
49
|
+
- Check Node `>=18` and a clean `npm install`
|
|
50
|
+
- `atom --help` should print usage without starting the TUI. If that fails, check `npm run build` and `npm run typecheck` output first
|
|
51
|
+
|
|
52
|
+
## Reporting a bug
|
|
53
|
+
|
|
54
|
+
Include: command run, provider and model from the status line, token segment text, approval mode, full error string (shortest decisive line, not a dump), and what `npm test` plus `npm run typecheck` report.
|