@askalf/dario 5.4.15 → 5.4.17

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.
@@ -0,0 +1,248 @@
1
+ # dario + OpenClaw — battletested setup
2
+
3
+ End-to-end walkthrough for running OpenClaw through dario so it routes against your Claude Pro / Max subscription instead of paying per-token API rates. Covers install → config → first run → verification → the gotchas that bite first-time users — including the `openclaw.inbound_meta.v1` classifier filter that dario's default mode silently protects against.
4
+
5
+ This is opinionated. There are several ways to wire OpenClaw to dario; this is the one we run in production and trust to not surprise us. Where we omit options it's because they're worse, not because they don't exist.
6
+
7
+ ## What you'll have at the end
8
+
9
+ - OpenClaw running locally, talking to dario at `localhost:3456`
10
+ - All Claude API calls routed through your Pro / Max subscription via the Claude Code wire shape
11
+ - OpenClaw's tool schema (`exec`, `process`, `web_search`, `web_fetch`, `browser`, `message`) auto-translated to CC's canonical set on the outbound path and rebuilt back on the inbound path — **no flag required**
12
+ - Your `openclaw.inbound_meta.v1` namespace stripped at the proxy boundary so Anthropic's billing classifier doesn't flip you to extra-usage
13
+ - `dario doctor --usage` showing the OpenClaw traffic in your 5-hour bucket with `claim=five_hour (subscription)`
14
+
15
+ Total install + config: 5–10 minutes if dario is already installed.
16
+
17
+ ## Prerequisites
18
+
19
+ | Thing | Version | Why |
20
+ |---|---|---|
21
+ | **OpenClaw** | 2026.2.17+ recommended | Older versions read auth differently — see auth-profiles gotcha below |
22
+ | **dario** | v3.31.2+ (latest preferred — `npm i -g @askalf/dario@latest`) | Auth-mismatch reject log, structural-fallback tool detection, classifier-fingerprint protection |
23
+ | **A Claude OAuth login** | run `dario login` once | The whole point — a Pro / Max subscription on a Claude account |
24
+ | **Bun** (recommended) | 1.1+ | dario auto-relaunches under Bun for TLS-fingerprint fidelity. Skip if you're fine with a runtime banner; install via [bun.sh](https://bun.sh) for the full subscription wire shape. |
25
+
26
+ Verify dario before starting OpenClaw — saves you from chasing OpenClaw errors that are actually dario auth issues:
27
+
28
+ ```bash
29
+ dario doctor # all green = ready
30
+ dario status # OAuth healthy, expires in N hours
31
+ ```
32
+
33
+ If `dario status` shows expired or missing OAuth, run `dario login` and retry. Don't continue past this step until both are clean.
34
+
35
+ ## Install OpenClaw
36
+
37
+ Follow the install instructions in [OpenClaw's own README](https://github.com/openclaw/openclaw) — installation steps move with their releases and we'd rather link the canonical source than copy-paste a snapshot that goes stale.
38
+
39
+ Verify after install:
40
+
41
+ ```bash
42
+ openclaw --version
43
+ ```
44
+
45
+ The rest of this guide assumes a working `openclaw` CLI on your `PATH`.
46
+
47
+ ## Configure OpenClaw → dario
48
+
49
+ OpenClaw reads its Anthropic configuration from three places, in priority order (2026.2.17+):
50
+
51
+ 1. `~/.openclaw/agents/main/agent/auth-profiles.json` — wins if the `anthropic:default` entry exists
52
+ 2. `openclaw.json` — `apiKey` field
53
+ 3. `ANTHROPIC_API_KEY` and `ANTHROPIC_BASE_URL` env vars — fallback
54
+
55
+ **This priority order is the single most common source of "why doesn't dario see my requests" tickets** ([dario#97](https://github.com/askalf/dario/issues/97)). Read the next section carefully.
56
+
57
+ ### Step 1 — Set the env vars
58
+
59
+ Put this in your shell profile (`~/.bashrc`, `~/.zshrc`, fish `config.fish`, or PowerShell `$PROFILE`):
60
+
61
+ ```bash
62
+ export ANTHROPIC_BASE_URL=http://localhost:3456
63
+ export ANTHROPIC_API_KEY=dario
64
+ ```
65
+
66
+ Two things to know:
67
+
68
+ - `ANTHROPIC_BASE_URL=http://localhost:3456` — points OpenClaw at dario instead of `api.anthropic.com`. dario speaks the Anthropic protocol on this port.
69
+ - `ANTHROPIC_API_KEY=dario` — this string is a literal placeholder. dario doesn't validate it (the real auth lives in dario's stored OAuth token). If you're running dario with `DARIO_API_KEY` set (LAN/multi-host mode), use that value here instead.
70
+
71
+ ### Step 2 — Clear or overwrite the auth-profiles.json entry
72
+
73
+ If you've ever set up OpenClaw with a real Anthropic API key, the env vars above won't take effect — `auth-profiles.json` wins. Pick one of two fixes:
74
+
75
+ **Option A: delete the Anthropic entry from auth-profiles.json (preferred — confirmed working by [@tetsuco in dario#97](https://github.com/askalf/dario/issues/97))**
76
+
77
+ Open `~/.openclaw/agents/main/agent/auth-profiles.json` in any editor, remove the `"anthropic:default"` entry (or the whole file if Anthropic is the only profile in there), save. OpenClaw falls back to the env vars on next run.
78
+
79
+ **Option B: overwrite the auth-profiles.json entry with `dario`**
80
+
81
+ ```bash
82
+ openclaw models auth paste-token --provider anthropic
83
+ # When prompted, paste the literal string: dario
84
+ ```
85
+
86
+ This replaces whatever was in the file with `dario`. Keep a backup if you use the original key elsewhere.
87
+
88
+ > **Loopback escape hatch.** If you're running dario at `--host=127.0.0.1` (the default), you don't need `DARIO_API_KEY` set at all and the auth-profiles content matters less — dario only enforces auth on non-loopback binds. So if you can stay on loopback, do; the auth-profiles dance is only needed for LAN reach setups (Tailscale, multi-host).
89
+
90
+ ### Step 3 — Verify the config takes effect
91
+
92
+ ```bash
93
+ echo "$ANTHROPIC_BASE_URL"
94
+ # expected: http://localhost:3456
95
+ ```
96
+
97
+ If that's empty, restart your shell or `source` your profile.
98
+
99
+ ### Battletested model choices
100
+
101
+ OpenClaw exposes its model selection through its own config. We default to:
102
+
103
+ | Model | When to use | Notes |
104
+ |---|---|---|
105
+ | `claude-sonnet-4-6` | **Default for everything.** | Best quality/speed for agent loops. What we run 95% of the time. |
106
+ | `claude-opus-5` | Hard reasoning tasks, refactors, novel architecture | ~3× slower per turn, ~3× more tokens. Subscription absorbs the cost; your wall-clock pays. Worth it for one-shot heavy lifts, overkill for routine. |
107
+ | `claude-haiku-4-5` | Fast scripts, quick lookups, smoke tests | Cheap on tokens, weak on multi-step reasoning. Don't run a long autonomous OpenClaw session on Haiku. |
108
+
109
+ Set the model in OpenClaw's config (the field name varies by OpenClaw version — check `openclaw config --help`). Use the canonical Anthropic model ID (no provider prefix) — OpenClaw is already on the Anthropic protocol.
110
+
111
+ ## First run
112
+
113
+ Start dario in one terminal:
114
+
115
+ ```bash
116
+ dario proxy --verbose
117
+ ```
118
+
119
+ Leave it running. You'll watch real-time request logs here.
120
+
121
+ In a second terminal, run an OpenClaw task:
122
+
123
+ ```bash
124
+ openclaw "Add a function to utils.py that takes a list of strings and returns them sorted by length, longest first. Include a docstring and a basic test."
125
+ ```
126
+
127
+ (Exact invocation may differ by OpenClaw version — check `openclaw --help`. The above is the conceptual shape; substitute the right flags for your install.)
128
+
129
+ Watch the dario terminal — you should see one log line per request, looking like:
130
+
131
+ ```
132
+ [dario] #1 POST /v1/messages (model: claude-sonnet-4-6) → 200 (1843 ms)
133
+ [dario] #2 POST /v1/messages (model: claude-sonnet-4-6) → 200 (2104 ms)
134
+ ...
135
+ ```
136
+
137
+ If you see `→ 200` lines and OpenClaw is making progress, you're good. dario's `client: 'unknown-non-cc'` structural fallback is silently auto-translating OpenClaw's `exec` / `process` / `web_search` / `web_fetch` / `browser` / `message` tools to CC's canonical set on the outbound path and rebuilding the OpenClaw shape on the inbound path — no flag, no config.
138
+
139
+ ## Verifying subscription billing (the important part)
140
+
141
+ This is what separates "OpenClaw talking to dario" from "OpenClaw actually using your Claude subscription." Two checks:
142
+
143
+ ### Check 1: dario doctor --usage
144
+
145
+ ```bash
146
+ dario doctor --usage
147
+ ```
148
+
149
+ You should see your 5-hour bucket showing non-zero usage with `claim=five_hour (subscription)`:
150
+
151
+ ```
152
+ [ OK ] Usage 5h (all) 12.3% used • status=allowed • claim=five_hour (subscription)
153
+ ```
154
+
155
+ If `claim=five_hour (subscription)` shows up, you're billing against subscription, not API. Done.
156
+
157
+ If `claim=api` shows up, something flipped you to per-token billing. The most common cause for OpenClaw users is the next section.
158
+
159
+ ### Check 2: Anthropic dashboard
160
+
161
+ Log into [console.anthropic.com](https://console.anthropic.com) → Usage. **Your API spend should be flat** (no new charges since you started OpenClaw). If it's climbing, something is bypassing dario.
162
+
163
+ ## How dario protects you from the classifier filter
164
+
165
+ Anthropic's billing classifier fingerprints the string `openclaw.inbound_meta.v1` and routes any request containing it to extra-usage billing — not subscription. This was reproduced in [dario discussion #178](https://github.com/askalf/dario/discussions/178) following Theo Browne's original finding.
166
+
167
+ The filter triggers on the `openclaw.inbound_meta.v1` namespace appearing in the request body — which is easy to hit accidentally. If you've ever made a commit message or branch name containing that string in any of your repos, Claude Code's environment block (which CC adds to its prompt) will surface those names back to the model in the system prompt, and the classifier flips your request.
168
+
169
+ **dario's default template-replay mode protects you from this automatically.** Every outbound request gets rebuilt from dario's captured-fresh CC system prompt — your local git context (commit messages, branch names, modified-file lists) is discarded at the proxy boundary. The `openclaw.inbound_meta.v1` string never leaves your machine.
170
+
171
+ You don't need to do anything to get this protection — it's the default. The protection is validated by `scripts/research/test-dario-protects-openclaw.mjs` against real Anthropic upstream traffic.
172
+
173
+ > **If you want to verify it on your own machine:** create a temp git repo with `{"schema": "openclaw.inbound_meta.v1"}` as a commit message, run OpenClaw against dario from inside that repo, and check `dario doctor --usage` — you should still see `claim=five_hour`. Without dario, the same setup would 400 or flip to api-billing.
174
+
175
+ ## Common gotchas
176
+
177
+ ### `401 Unauthorized` from dario, even though `ANTHROPIC_API_KEY=dario` is set
178
+
179
+ This is the auth-profiles.json priority issue ([dario#97](https://github.com/askalf/dario/issues/97)). OpenClaw is reading a stale Anthropic key from `~/.openclaw/agents/main/agent/auth-profiles.json` instead of your env var. Run `dario proxy -v` to confirm — you'll see `Authorization present but value mismatch (header: x-api-key)` in the reject log. Fix with one of the three options in Step 2 above.
180
+
181
+ ### `Connection refused` to localhost:3456
182
+
183
+ dario isn't running, or it's bound to a different port:
184
+
185
+ ```bash
186
+ curl -s http://localhost:3456/health
187
+ # expected: {"status":"ok",...}
188
+ ```
189
+
190
+ If that fails, start dario (`dario proxy --verbose`). If it's running on a custom port, match it: `ANTHROPIC_BASE_URL=http://localhost:<port>`.
191
+
192
+ ### `claim=api` showing up in dario doctor --usage
193
+
194
+ Something is sneaking the `openclaw.inbound_meta.v1` string past dario's template replay. The most common cause is running with `dario proxy --passthrough` (thin proxy, no template injection) — passthrough mode does an OAuth swap only and does NOT scrub the system prompt, so any classifier triggers in your local env survive. Drop the `--passthrough` flag and the default template-replay mode will protect you.
195
+
196
+ If you actually need passthrough mode for some other reason, you'll need to clean your local git state — rename branches with `openclaw` in their names, rewrite commit messages with that string, and remove any files in your working tree that contain the classifier-fingerprint namespace.
197
+
198
+ ### Long sessions failing with rate limits
199
+
200
+ If you run multiple parallel OpenClaw sessions on a single Claude account, you'll hit the 5-hour rate limit fast. Two fixes:
201
+
202
+ 1. **Add a second account to dario's pool** (`dario accounts add work` after running `dario login` on a second account). Pool mode routes each request to whichever account has the most headroom and uses session stickiness so multi-turn chats stay on one account. See [`docs/multi-account-pool.md`](./multi-account-pool.md).
203
+ 2. **Stagger the runs** — finish one before starting the next. Cheaper, no extra subscription needed.
204
+
205
+ ### Tools come back empty / wrong shape
206
+
207
+ Symptom: OpenClaw's tool calls round-trip with stripped fields, or your runtime complains about a required field being absent only when routed through dario.
208
+
209
+ If you see this, run dario with `--preserve-tools`:
210
+
211
+ ```bash
212
+ dario proxy --preserve-tools
213
+ ```
214
+
215
+ This skips the CC tool remap entirely and forwards OpenClaw's tool definitions through to the model unchanged. You lose the CC wire shape (and may lose subscription billing), but you keep all custom tool fields. Reserve for cases where the auto-detection can't reconstruct your schema cleanly.
216
+
217
+ ## What this guide doesn't cover
218
+
219
+ - **OpenClaw's own internal config flags.** OpenClaw has a rich CLI with options for sandbox, project root, agent loop tuning, etc. We don't shadow them here; check `openclaw --help`. The dario integration is at the LLM layer; agent code doesn't need to change.
220
+ - **OpenClaw derivatives** (forks like `openclaw-billing-proxy`, `nanoclaw`, etc.). These typically work the same way — point them at `localhost:3456` and dario's structural-fallback tool detection (the 3+ tools, ≥80% not in TOOL_MAP rule) catches them automatically. If a specific fork doesn't work, open an issue with the request body shape and we'll add explicit detection.
221
+
222
+ ## Quick reference card
223
+
224
+ ```bash
225
+ # One-time setup
226
+ # (install OpenClaw per its README)
227
+ npm install -g @askalf/dario
228
+ dario login
229
+
230
+ # Per-session
231
+ export ANTHROPIC_BASE_URL=http://localhost:3456
232
+ export ANTHROPIC_API_KEY=dario
233
+ # (clear ~/.openclaw/agents/main/agent/auth-profiles.json first if you have a stale Anthropic key)
234
+ dario proxy --verbose &
235
+ openclaw "your task here"
236
+
237
+ # Verify subscription billing
238
+ dario doctor --usage # claim=five_hour (subscription) ✓
239
+ ```
240
+
241
+ ## Related guides
242
+
243
+ - [`openhands-walkthrough.md`](./openhands-walkthrough.md) — sister walkthrough for OpenHands
244
+ - [`agent-compat.md`](./agent-compat.md) — short setup snippets for every other agent dario supports
245
+ - [`multi-account-pool.md`](./multi-account-pool.md) — adding 2+ Claude accounts to extend rate limits
246
+ - [`commands.md`](./commands.md) — full dario CLI reference
247
+ - [`faq.md`](./faq.md) — common dario questions, including the OpenClaw auth-profiles 401 entry
248
+ - [`research/system-prompt-classifier-study.md`](./research/system-prompt-classifier-study.md) — empirical work on what the billing classifier reads (and doesn't)
@@ -0,0 +1,255 @@
1
+ # dario + OpenHands — battletested setup
2
+
3
+ End-to-end walkthrough for running [OpenHands](https://github.com/All-Hands-AI/OpenHands) (the open-source software-engineer agent, formerly OpenDevin) through dario so it routes against your Claude Pro / Max subscription instead of paying per-token API rates. Covers install → config → first run → verification → the gotchas that bite first-time users.
4
+
5
+ This is opinionated. There are several ways to wire OpenHands to dario; this is the one we run in production and trust to not surprise us. Where we omit options it's because they're worse, not because they don't exist.
6
+
7
+ ## What you'll have at the end
8
+
9
+ - OpenHands running locally, talking to dario at `localhost:3456`
10
+ - All Claude API calls routed through your Pro / Max subscription via the Claude Code wire shape
11
+ - Multi-turn agent loops running on subscription billing, not per-token
12
+ - `dario doctor --usage` showing the OpenHands traffic in your 5-hour bucket
13
+ - A working `--task` invocation you can drop into a script
14
+
15
+ Total install + config: 10–15 minutes if Python and dario are already installed.
16
+
17
+ ## Prerequisites
18
+
19
+ | Thing | Version | Why |
20
+ |---|---|---|
21
+ | **Python** | 3.12+ | OpenHands' minimum |
22
+ | **Poetry** or **pipx** | recent | OpenHands' install flow |
23
+ | **dario** | v3.30+ (latest preferred — `npm i -g @askalf/dario@latest`) | OpenAI-compat endpoint plus the provider-prefix routing this guide leans on |
24
+ | **A Claude OAuth login** | run `dario login` once | The whole point — a Pro / Max subscription on a Claude account |
25
+ | **Bun** (recommended) | 1.1+ | dario auto-relaunches under Bun for TLS-fingerprint fidelity. Skip if you're fine with a runtime banner; install via [bun.sh](https://bun.sh) for the full subscription wire shape. |
26
+
27
+ Verify dario before starting OpenHands install — saves you from chasing OpenHands errors that are actually dario auth issues:
28
+
29
+ ```bash
30
+ dario doctor # all green = ready
31
+ dario status # OAuth healthy, expires in N hours
32
+ ```
33
+
34
+ If `dario status` shows expired or missing OAuth, run `dario login` and retry. Don't continue past this step until both are clean.
35
+
36
+ ## Install OpenHands
37
+
38
+ The official path uses `pipx` for a clean install that doesn't fight your system Python:
39
+
40
+ ```bash
41
+ pipx install openhands-ai
42
+ ```
43
+
44
+ If you don't have pipx: `python -m pip install --user pipx && pipx ensurepath`, then start a new shell.
45
+
46
+ Verify:
47
+
48
+ ```bash
49
+ openhands --version
50
+ ```
51
+
52
+ If you'd rather run from a clone (faster iteration on agent code, harder to keep updated):
53
+
54
+ ```bash
55
+ git clone https://github.com/All-Hands-AI/OpenHands.git
56
+ cd OpenHands
57
+ poetry install
58
+ ```
59
+
60
+ Both paths give you the `openhands` CLI. The rest of this guide assumes the pipx path.
61
+
62
+ ## Configure OpenHands → dario
63
+
64
+ OpenHands reads config from environment variables and an optional `config.toml`. We use environment variables for everything because they're easier to swap when testing different Claude models or pool accounts.
65
+
66
+ ### The minimum working config
67
+
68
+ Put this in your shell profile (`~/.bashrc`, `~/.zshrc`, `~/.config/fish/config.fish`, or PowerShell `$PROFILE`):
69
+
70
+ ```bash
71
+ export LLM_BASE_URL=http://localhost:3456
72
+ export LLM_API_KEY=dario
73
+ export LLM_MODEL=anthropic/claude-sonnet-4-6
74
+ ```
75
+
76
+ Three things to know about each line:
77
+
78
+ - `LLM_BASE_URL=http://localhost:3456` — points OpenHands at dario instead of `api.anthropic.com`. dario speaks both Anthropic and OpenAI protocols on this port; OpenHands picks Anthropic because of the model name in the next line.
79
+ - `LLM_API_KEY=dario` — this string is a literal placeholder. dario doesn't validate it; the real auth lives in dario's stored OAuth token. Putting a dummy value here is intentional. If you're running dario with `DARIO_API_KEY` set (LAN/multi-host mode), use that value here instead.
80
+ - `LLM_MODEL=anthropic/claude-sonnet-4-6` — the `anthropic/` prefix tells LiteLLM (OpenHands' inner routing layer) "use the Anthropic protocol" — that's what makes OpenHands hit dario's `/v1/messages` endpoint instead of `/v1/chat/completions`. Without the prefix, LiteLLM defaults to OpenAI shape and dario will translate it but you lose subscription-billing fidelity. **Always use the `anthropic/` prefix.**
81
+
82
+ ### Battletested model choices
83
+
84
+ | Model | When to use | Notes |
85
+ |---|---|---|
86
+ | `anthropic/claude-sonnet-4-6` | **Default for everything.** | Best quality/speed for agent loops. What we run 95% of the time. |
87
+ | `anthropic/claude-opus-5` | Hard reasoning tasks, refactors, novel architecture | ~3× slower per turn, ~3× more tokens. Subscription absorbs the cost; your wall-clock pays. Worth it for one-shot heavy lifts, overkill for routine. |
88
+ | `anthropic/claude-haiku-4-5` | Fast scripts, quick lookups, smoke tests | Cheap on tokens, weak on multi-step reasoning. Don't run a multi-hour agent loop on Haiku. |
89
+
90
+ We do **not** recommend mixing models inside a single OpenHands run via the `LLM_DRAFT_MODEL` setting — the runtime overhead of switching wire shapes mid-conversation costs more than the smaller-model savings.
91
+
92
+ ### Optional but useful
93
+
94
+ ```bash
95
+ # Maximum tokens per response (default is conservative)
96
+ export LLM_MAX_OUTPUT_TOKENS=8192
97
+
98
+ # Cap input context to stay safely under Anthropic's per-model ceiling.
99
+ # Sonnet 4.6 supports 200k natively; OpenHands defaults to 128k. Leave at default
100
+ # unless you've seen 'context too long' errors during long sessions.
101
+ # export LLM_MAX_INPUT_TOKENS=128000
102
+
103
+ # Temperature — keep low for code agents.
104
+ export LLM_TEMPERATURE=0.0
105
+ ```
106
+
107
+ ### Verifying the config loads
108
+
109
+ ```bash
110
+ echo "$LLM_BASE_URL $LLM_MODEL"
111
+ # expected: http://localhost:3456 anthropic/claude-sonnet-4-6
112
+ ```
113
+
114
+ If those don't print, the env vars didn't load — restart your shell or `source` your profile.
115
+
116
+ ## First run
117
+
118
+ Start dario in one terminal:
119
+
120
+ ```bash
121
+ dario proxy --verbose
122
+ ```
123
+
124
+ Leave it running. You'll watch real-time request logs here.
125
+
126
+ In a second terminal, run an OpenHands task:
127
+
128
+ ```bash
129
+ openhands --task "Add a function to utils.py that takes a list of strings and returns them sorted by length, longest first. Include a docstring and a basic test."
130
+ ```
131
+
132
+ OpenHands will:
133
+
134
+ 1. Spin up its workspace
135
+ 2. Ask Claude (via dario) what to do
136
+ 3. Execute file edits, run tests, and iterate
137
+
138
+ Watch the dario terminal — you should see one log line per request, looking like:
139
+
140
+ ```
141
+ [dario] #1 POST /v1/messages (model: claude-sonnet-4-6) → 200 (1843 ms)
142
+ [dario] #2 POST /v1/messages (model: claude-sonnet-4-6) → 200 (2104 ms)
143
+ ...
144
+ ```
145
+
146
+ If you see `→ 200` lines and OpenHands is making progress, you're good.
147
+
148
+ ## Verifying subscription billing (the important part)
149
+
150
+ This is what separates "OpenHands talking to dario" from "OpenHands actually using your Claude subscription." Two checks:
151
+
152
+ ### Check 1: dario doctor --usage
153
+
154
+ ```bash
155
+ dario doctor --usage
156
+ ```
157
+
158
+ You should see your 5-hour bucket showing non-zero usage with `claim=five_hour (subscription)`:
159
+
160
+ ```
161
+ [ OK ] Usage 5h (all) 12.3% used • status=allowed • claim=five_hour (subscription)
162
+ ```
163
+
164
+ If `claim=five_hour (subscription)` shows up, you're billing against subscription, not API. Done.
165
+
166
+ If `claim=api` shows up, something flipped you to per-token billing — usually the model name is wrong (missing `anthropic/` prefix) or you set `DARIO_API_KEY` and OpenHands didn't pass it correctly.
167
+
168
+ ### Check 2: Anthropic dashboard
169
+
170
+ Log into [console.anthropic.com](https://console.anthropic.com) → Usage. **Your API spend should be flat** (no new charges since you started OpenHands). If it's climbing, something is bypassing dario.
171
+
172
+ ## Common gotchas
173
+
174
+ ### `Error: model not found: anthropic/claude-sonnet-4-6`
175
+
176
+ LiteLLM's local model registry is out of date. Two options:
177
+
178
+ - Upgrade OpenHands: `pipx upgrade openhands-ai`
179
+ - OR set the model without the `anthropic/` prefix and let dario's `claude-*` regex catch it: `LLM_MODEL=claude-sonnet-4-6`. This loses some LiteLLM routing intelligence but works. You may also use dario's provider-prefix syntax: `LLM_MODEL=claude:claude-sonnet-4-6`.
180
+
181
+ ### `Connection refused` to localhost:3456
182
+
183
+ dario isn't running, or it's bound to a different port:
184
+
185
+ ```bash
186
+ curl -s http://localhost:3456/health
187
+ # expected: {"status":"ok",...}
188
+ ```
189
+
190
+ If that fails, start dario (`dario proxy --verbose`). If it's running but on a custom port, match it: `LLM_BASE_URL=http://localhost:<port>`.
191
+
192
+ ### OpenHands hangs at "thinking..." for >60 seconds, then times out
193
+
194
+ dario's outbound timeout to Anthropic is 5 min by default; if OpenHands gives up sooner, set its retry config:
195
+
196
+ ```bash
197
+ export LLM_NUM_RETRIES=3
198
+ export LLM_RETRY_MIN_WAIT=2
199
+ export LLM_RETRY_MAX_WAIT=60
200
+ ```
201
+
202
+ This makes OpenHands more patient. Anthropic occasionally serves slow first-token latency on subscription traffic; the retry layer absorbs it cleanly.
203
+
204
+ ### Long sessions fail with `context_length_exceeded`
205
+
206
+ OpenHands' context manager doesn't always trim aggressively enough. If a session runs hot, set:
207
+
208
+ ```bash
209
+ export LLM_MAX_INPUT_TOKENS=180000 # Sonnet 4.6 native ceiling minus headroom
210
+ ```
211
+
212
+ For very long autonomous runs, switch the agent to `--config-file` mode and enable OpenHands' context-condensation feature explicitly. The TOML config has more knobs than the env vars expose.
213
+
214
+ ### Multiple OpenHands sessions starve a single Claude account
215
+
216
+ If you run two or more parallel OpenHands sessions, you'll hit the 5-hour rate limit fast. Two fixes:
217
+
218
+ 1. **Add a second account to dario's pool** (`dario accounts add work` after running `dario login` on a second account). Pool mode routes each request to whichever account has the most headroom and uses session stickiness so multi-turn chats stay on one account. See [`docs/multi-account-pool.md`](./multi-account-pool.md).
219
+ 2. **Stagger the runs** — finish one before starting the next. Cheaper, no extra subscription needed.
220
+
221
+ ### "Claim flipped to api during a session"
222
+
223
+ Anthropic's billing classifier occasionally rejects subscription traffic if the request body fingerprint drifts. dario captures CC's exact wire shape weekly and pins it; if you see this happen, run `dario doctor` first — it'll show whether your CC version is current. If `template: live capture, CC vX.Y.Z (Nh old)` is more than 48 hours old, do `dario doctor --bun-bootstrap` to force a fresh capture.
224
+
225
+ ## What this guide doesn't cover
226
+
227
+ - **OpenHands web UI** (`openhands serve`). Same env vars work; the walkthrough above is TUI/CLI-focused because that's where battletesting time has gone. The web UI works but we don't run it routinely.
228
+ - **Custom OpenHands agents.** Subclassing `Agent` to write your own controller is supported but out of scope here. The dario integration is at the LLM layer; agent code doesn't need to change.
229
+ - **Sandbox configuration.** OpenHands runs commands in Docker by default. dario doesn't care which sandbox you use; pick whichever your security model requires.
230
+
231
+ ## Quick reference card
232
+
233
+ ```bash
234
+ # One-time setup
235
+ pipx install openhands-ai
236
+ npm install -g @askalf/dario
237
+ dario login
238
+
239
+ # Per-session
240
+ export LLM_BASE_URL=http://localhost:3456
241
+ export LLM_API_KEY=dario
242
+ export LLM_MODEL=anthropic/claude-sonnet-4-6
243
+ dario proxy --verbose &
244
+ openhands --task "your task here"
245
+
246
+ # Verify subscription billing
247
+ dario doctor --usage # claim=five_hour (subscription) ✓
248
+ ```
249
+
250
+ ## Related guides
251
+
252
+ - [`agent-compat.md`](./agent-compat.md) — short setup snippets for every other agent dario supports (Cursor, Continue, Aider, Cline, Zed, GitHub Copilot, etc.)
253
+ - [`multi-account-pool.md`](./multi-account-pool.md) — adding 2+ Claude accounts to extend rate limits
254
+ - [`commands.md`](./commands.md) — full dario CLI reference
255
+ - [`faq.md`](./faq.md) — common dario questions independent of which agent you're running
@@ -0,0 +1,22 @@
1
+ # dario as MCP server (v3.27)
2
+
3
+ `dario mcp` turns dario itself into a **stdio JSON-RPC 2.0 MCP server**. Claude Desktop, Cursor, Zed, any MCP-aware editor can introspect dario's state without leaving the editor.
4
+
5
+ ```bash
6
+ dario mcp # spawns the MCP server on stdin/stdout — wire it up to your MCP client
7
+ ```
8
+
9
+ **Strictly read-only.** The exposed tool set is:
10
+
11
+ | Tool | What it reports |
12
+ |---|---|
13
+ | `doctor` | Full aggregated health report — same output as `dario doctor` |
14
+ | `status` | OAuth authentication state (authenticated / no-credentials / expired-but-refreshable) |
15
+ | `accounts_list` | Pool accounts + expiry times. Never touches API keys. |
16
+ | `backends_list` | Configured OpenAI-compat backends — keys redacted completely (not even a `sk-…` prefix) |
17
+ | `subagent_status` | CC sub-agent install and version-match state |
18
+ | `fingerprint_info` | Runtime / TLS classification, template source + schema version |
19
+
20
+ Mutations (`login`, `logout`, `accounts add/remove`, `backend add/remove`, `subagent install/remove`, `proxy` start/stop) are **not** exposed. An MCP client can observe dario; changing dario's state stays a CLI action the user types with intent. The test suite asserts the forbidden-tool set stays forbidden so a future accidental drift gets caught.
21
+
22
+ Zero runtime deps — the JSON-RPC dispatcher is hand-rolled over Node's `readline`. `src/mcp/protocol.ts` + `src/mcp/tools.ts` + `src/mcp/server.ts` are each pure over their inputs (streams are injectable, data sources are injectable) so the e2e test runs in-process against a `PassThrough` pair.
@@ -0,0 +1,68 @@
1
+ # The account pool
2
+
3
+ As of v5.0 the account pool is dario's one credential model. A plain `dario login` is a **pool of one** (materialized as `~/.dario/accounts/login.json` under the reserved `login` alias); adding accounts just makes it a pool of many. There's no separate single-account mode — a pool of one and a pool of many run the identical request path.
4
+
5
+ ```bash
6
+ dario login # a pool of one
7
+ dario accounts add work # now a pool of two
8
+ dario accounts add personal
9
+ dario accounts list
10
+ dario proxy
11
+ ```
12
+
13
+ Your `dario login` credentials materialize into the pool automatically — on `dario login` itself, and again on `dario proxy` startup as a safety net. `~/.dario/credentials.json` is left in place; the back-fill is a one-way copy, never a move. If you run `dario accounts add <alias>` on top of a login-only setup, the `login` account is already in the pool, so you simply gain the new alias alongside it. Picking `login` as an explicit alias is your call — dario won't clobber it.
14
+
15
+ Each request picks the account with the highest headroom:
16
+
17
+ ```
18
+ headroom = 1 - max(util_5h, util_7d)
19
+ ```
20
+
21
+ The response's `anthropic-ratelimit-unified-*` headers are parsed back into the pool so the next selection sees fresh utilization. An account that returns a 429 is marked `rejected` and routed around until its window resets. When every account is exhausted, requests queue for up to 60 seconds waiting for headroom to reappear. Plan tiers mix freely in the same pool — dario doesn't care about tier, only headroom.
22
+
23
+ ## Routing strategy
24
+
25
+ Headroom spreading is the default and stays the right call when every seat is equal. `--pool-strategy=fill-first` (env `DARIO_POOL_STRATEGY`, config `pool.strategy`) flips to concentration: new conversations land on the **alphabetically-first** eligible seat until its headroom drains to the 2% floor, then spill to the next alias in line. Failover follows the same order — after a 429 the retry goes to the next alias, not the max-headroom seat.
26
+
27
+ Two situations where that beats spreading:
28
+
29
+ - **Primary/backup seats.** A `z-backup` account stays completely untouched — fresh 5h and 7d windows — until `a-main` is actually drained. Headroom spreading would nibble at both from the first request.
30
+ - **Cache concentration.** Every fresh conversation lands where the prompt-cache pressure already is, so the spill seat's windows are fully fresh when the primary hits its wall.
31
+
32
+ Alias order is the operator's knob: name seats `1-main` / `2-overflow` to pick the fill order. Strategy only decides where **unbound** conversations land — sticky bindings (below) behave identically in both modes, and a conversation bound to a seat stays there until that seat is rejected, expiring, or under the floor.
33
+
34
+ ## Session stickiness
35
+
36
+ Multi-turn agent sessions pin to one account for the life of the conversation, so the Anthropic prompt cache isn't destroyed by account rotation between turns.
37
+
38
+ **The problem.** Claude prompt cache is scoped to `{account × cache_control key}`. When the pool rotates a long agent conversation across accounts on headroom alone, turn 1 builds a cache entry on account A, turn 2 lands on account B and reads nothing from A's cache — paying full cache-create cost again. For a long agent session that's a **5–10× token-cost multiplier** on every turn after the first.
39
+
40
+ **The fix.** Dario hashes a conversation's first user message into a 16-hex-char `stickyKey` (SHA-256 truncated, deterministic) and binds the key to whichever account `select()` would have picked on turn 1. Subsequent turns re-use that account as long as it's still healthy (not rejected, token not near expiry, headroom > 2%). On 429 failover, dario rebinds the key to the new account so the next turn doesn't re-select the exhausted one. 6h TTL, 2,000-entry cap, lazy cleanup. No client cooperation required.
41
+
42
+ ## Pool-exhausted fallback
43
+
44
+ `--pool-fallback=<model>` (env `DARIO_POOL_FALLBACK`, config `poolFallback.model`) is a strictly opt-in escape hatch for when the whole pool is drained or cooling. With it set **and** an openai-compat backend configured (`dario backend add …`), an OpenAI-shape request (`/v1/chat/completions`) that the pool can't serve — at selection time, or after a mid-flight 429 with no peer left — is forwarded to that backend with the model swapped to `<model>`, instead of returning the 429/503.
45
+
46
+ Three deliberate limits:
47
+
48
+ - **OpenAI-shape only.** Anthropic-shape requests (`/v1/messages`) keep the error. dario translates Anthropic→OpenAI on the way out but has no OpenAI→Anthropic *response* translator, so a fallback there would corrupt the client's stream. Point Anthropic-native clients that want this at `/v1/chat/completions`, or leave the fallback off.
49
+ - **Never silent.** Every substituted response carries `x-dario-pool-fallback: <model>`. A quietly swapped model is exactly the surprise this project exists to avoid — check for the header if you need to know which requests fell back.
50
+ - **Empty pool still errors.** A pool with zero accounts is a setup mistake (`dario login` never ran); that returns the usual 503 rather than silently re-billing every request to another provider.
51
+
52
+ ```bash
53
+ dario backend add openrouter --key=sk-or-... --base-url=https://openrouter.ai/api/v1
54
+ dario proxy --pool-fallback=openrouter/anthropic/claude-3.5-sonnet
55
+ ```
56
+
57
+ ## In-flight 429 failover
58
+
59
+ When a Claude request hits a 429 mid-flight, dario retries the *same request* against a different account before the client sees an error. The client sees one successful response; the pool sees the rejected account go cold until its window resets. Combined with session stickiness, long agent runs survive pool-level exhaustion without dropping user-facing turns.
60
+
61
+ ## Inspection
62
+
63
+ ```bash
64
+ curl http://localhost:3456/accounts # per-account utilization, claim, sticky bindings, status
65
+ curl http://localhost:3456/analytics # per-account / per-model stats, burn rate, exhaustion predictions
66
+ ```
67
+
68
+ Every request carries a `billingBucket` field (`subscription` / `subscription_fallback` / `extra_usage` / `api` / `unknown`) so you can see which bucket each request billed against and a `subscriptionPercent` headline number tells you at a glance whether dario is actually routing through your subscription or silently falling to API overage.