cd /news/ai-agents/show-hn-the-price-of-patience Β· home β€Ί topics β€Ί ai-agents β€Ί article
[ARTICLE Β· art-169201] src=github.com β†— pub= topic=ai-agents verified=true sentiment=↑ positive

Show HN: The Price of Patience

A developer published a companion guide and a single-file TypeScript extension, doubleword.ts, that configures the pi coding agent against the Doubleword Inference API, pulling all 18 chat models live from Doubleword's catalog with per-token pricing mapped to pi's cost fields. The setup defaults every request to Doubleword's cheaper async tier via service_tier: "flex", switchable to realtime with /doubleword-mode realtime or the DOUBLEWORD_EXECUTION_MODE=realtime environment variable, and includes a verification pass for a silent failure that can prevent the system prompt, skills and rules from reaching the model. The guide pins installation to commit SHA 9a452e6, noting that pi executes extensions, so installing from a moving main branch is "policy you didn't choose.

read11 min views4 publishedNov 13, 2026
Show HN: The Price of Patience
Image: Michielbdejong (auto-discovered)

This is the companion guide to The Price of Patience β€” the story of how this setup was built, benchmarked, and audited. The article explains why; this README shows how.

How to configure the pi coding agent against the Doubleword Inference API β€” with every model pulled live from their catalog, the cheap async (service_tier: "flex") tier on by default, and a verification pass that catches the one silent failure that made my skills disappear.

A pi setup where:

  • GET /v1/models?include=pricing is fetched at startup, andall 18 chat models (GLM, Qwen, DeepSeek, Kimi, Gemma, …) appear withlive per-token pricing mapped to pi's cost fields β€” from the realtime, async, and batch tariffs Doubleword publishes per model.
  • Flex is the default tier : every request carriesservice_tier: "flex" , Doubleword's cheaper async execution mode. Flipping to realtime is one command:/doubleword-mode realtime (per session) orexport DOUBLEWORD_EXECUTION_MODE=realtime (global).
  • Your key can live wherever pi already keeps keys β€” environment variable, models.json , or/login storage β€” no juggling.
  • Catalog refresh, model switch, and tier switching all take effect live β€” the session rebinds to fresh model objects instead of waiting for a restart.
  • And critically: the system prompt β€” skills, rules, harness context β€” actually reaches the model. I'll show you the check for that, because on Doubleword it can silently fail.

Grab an API key from your Doubleword dashboard. pi reads provider keys from a few standard places; any one of these works:

Option A β€” environment variable (simplest):

export DOUBLEWORD_API_KEY="dw-sk-..."

Option B β€” pi's models.json (survives across shells):

{
  "providers": {
    "doubleword": {
      "name": "Doubleword",
      "baseUrl": "https://api.doubleword.ai/v1",
      "api": "openai-completions",
      "apiKey": "dw-sk-...",
      "models": []
    }
  }
}

(~/.pi/agent/models.json. The extension replaces the models list with the live catalog at startup; any entries you keep here serve as a fallback when the catalog is temporarily unreachable β€” one or two definitions are plenty.)

Option C β€” just /login in pi and pick the provider there.

The extension resolves all three, in that order, at request time. If it can't find one at startup it will ask pi's credential store on first refresh.

Save doubleword.ts into ~/.pi/agent/extensions/:

mkdir -p ~/.pi/agent/extensions
curl -o ~/.pi/agent/extensions/doubleword.ts \
  https://raw.githubusercontent.com/konarkmodi/pi-doubleword/9a452e6/doubleword.ts

(Pinned to a commit SHA: pi executes extensions, so installing from a moving main is policy you didn't choose. Bump the SHA deliberately when you adopt updates.)

(Or: clone the repo, copy the file. It's one TypeScript file, no dependencies.)

On next launch, pi loads it, fetches include=pricing immediately, and β€” if the catalog is reachable β€” replaces the fallback model list with live models. In pi you'll see:

Doubleword: 18 models available.

If your key was missing or the network was down, you'll see:

Doubleword: catalog unavailable (HTTP 401 ...). Run /doubleword-refresh after /login.

…then fix auth and run /doubleword-refresh β€” the fix applies to your open session immediately.

Set a model as your daily default in ~/.pi/agent/settings.json:

{
  "defaultProvider": "doubleword",
  "defaultModel": "zai-org/GLM-5.3-Flash",
  "defaultThinkingLevel": "medium"
}

Now launch pi bare β€” no --model flag needed. One-off runs work from any directory:

pi --model doubleword/zai-org/GLM-5.3-Flash "Reply with exactly OTTERS-OK"

About flex. Doubleword sells two ways to run a model: realtime (standard latency, standard price) and async (service tier "flex", discounted price β€” at the time of writing, e.g. GLM-5.3-Flash goes from $0.15/$0.50 to $0.11/$0.38 per MTok). Under the hood the extension stamps every live model with samplingParams: { service_tier: "flex" } β€” so every chat request carries it and the response echoes it back. Flip per session or globally:

/doubleword-mode realtime         # this session
/doubleword-mode async            # back to the default
export DOUBLEWORD_EXECUTION_MODE=realtime   # for the shell / CI

Realtime mode removes the service_tier sampling param entirely (there's nothing to echo). Your bill reflects whichever mode was active per request.

flex-bench.mjs puts a stopwatch on it β€” same prompt to both tiers, interleaved rounds (so time-of-day can't rig the comparison), costs computed from the live catalog rates:

MODEL=zai-org/GLM-5.3-Flash node flex-bench.mjs

A real run (GLM-5.3-Flash, Nov 2026, 5 interleaved rounds/tier):

realtime flex ( service_tier:"flex" ) Ξ”
Catalog rate (in/out per MTok) $0.150 / $0.500 $0.110 / $0.380 βˆ’27% in / βˆ’24% out
TTFT (median) 0.17 s 4.84 s +4.7 s
Total latency (median) 4.48 s 4.84 s +0.37 s (β‰ˆ8%)
Delivery progressive stream (~60–95 tok/s visible) one burst β€”
Tier echo (none) "flex" (non-streaming responses) β€”

How to read it:

  • You pay at the start, not the end. Flex's queue adds seconds tofirst token , but by the last token the two tiers are a few hundred milliseconds apart. For agents (which care when the job is done), flex is nearly free money. For a human watching a stream, it's the difference between a typist and an envelope.
  • Flex responses arrive as one burst β€” TTFT equals total latency (queued compute flushes complete). Anything building a typing UI should know this.
  • Reasoning-budget variance dominates per-request cost. Identical prompts ranged 211–1,992 reasoning tokens (3–9Γ—), so comparerates , not per-run medians.
  • Small sample, one model, one hour of the day β€” queueing varies. Run it yourself before quoting numbers.

This is the step I skipped β€” and it cost me. Here's what happened, in one paragraph: pi encodes its system prompt (harness rules, tool guidance, your <available_skills> block) as a developer-role message for reasoning-capable OpenAI-compatible models. OpenAI treats developer as the successor to system. In my tests with GLM-5.3-Flash, instructions sent through the developer role were not followed, despite the endpoint returning HTTP 200 β€” the same instructions sent through system were followed. The exact server-side cause (gateway drop, translation, priority handling) remains unknown; the operational outcome doesn't. Result: the tested instructions β€” harness rules, tool guidance, the <available_skills> block β€” were not acted on; the model answered questions correctly all day while improvising identities, denying skills, and inventing self-descriptions that changed between sessions. Whether every byte of the system prompt was absent is not established; nothing believed to be delivered behaved as delivered.

The fix is one line, already inside the extension:

compat: { supportsDeveloperRole: false },

This tells pi to send instructions as plain system β€” the role the tested endpoint demonstrably honors, and the long-standing convention across the compatible ecosystem. "Demonstrably" is the operative word: role handling is a per-endpoint, per-model fact, which is exactly why the checks below exist. Two checks, thirty seconds total:

Skill check. Ask pi directly:

pi --no-session --print "List the skill names advertised to you inside the \
<available_skills> block of your instructions. Comma-separated. If the block \
is missing, output exactly: NONE"

You should see your real skills β€” things like tldraw-offline, chrome-cdp, web-search, .... If you get NONE (or the model fabricates skill names that don't exist), your system prompt isn't arriving β€” that's the bug, not a config problem.

Magic-word check β€” the one test that cannot pass by luck. Adversarial negatives ("don't say X") pass whenever the model agrees with you by default. Positive recall can't: the answer exists in exactly one place, the injected instruction.

pi --no-session --print --append-system-prompt \
  "The secret word is PINEAPPLE-42." \
  "What is the secret word given in your instructions? Reply with just the word."

If it prints PINEAPPLE-42, instructions are delivered. If it claims there is no secret word β€” they aren't.

For a per-vendor, per-model verdict table (including a per-run nonce and one retry, so a model's occasional "I don't reveal my instructions" refusal doesn't pollute the result), I put together dev-role-probe.mjs β€” zero dependencies, points at any OpenAI-compatible endpoint:

MODEL=zai-org/GLM-5.3-Flash node dev-role-probe.mjs
Endpoint : https://api.doubleword.ai/v1
Model    : zai-org/GLM-5.3-Flash
Callsign : 5DF335-4A213B

system     PASS β€” model recalled the callsign
developer  FAIL  β‡’ replied: "GLM"

Verdicts it emits: both pass βœ… Β· developer-role instructions not followed HTTP 400 reject β›” (this one is real β€” some models on the same backend reject the developer role outright, meaning any modern OpenAI SDK client crashes on them). Interesting detail: on Doubleword, GLM does not follow developer-role instructions (HTTP 200, no effect) while Qwen rejects them β€” "OpenAI-compatible" describes request shape, not semantics.

The whole piece is self-verifying, with three commands:

MODEL=zai-org/GLM-5.3-Flash node flex-bench.mjs

AGENTX_INPUTS=~/projects/agentx-experiments/results/modal2-synth-c128/inputs.json \
  SESSIONS=3 TURNS=3 node agentx-cache-bench.mjs

MODEL=zai-org/GLM-5.3-Flash node dev-role-probe.mjs

time pi --no-session --print "Reply with the numbers 1 to 10."
time DOUBLEWORD_EXECUTION_MODE=realtime pi --no-session --print "Reply with the numbers 1 to 10."

Reference run of the first (5 interleaved rounds/tier): flex's published rates are 24–27% lower and estimated request costs came out lower on every matched pair; median TTFT 0.17 s (realtime) vs 4.84 s (flex), median total latency within ~0.4 s. time pi variant: ~1.8 s vs ~1.2 s whole-process. Your numbers will differ β€” that's why the scripts ship.

agentx-cache-bench.mjs replays AgentX aiperf session payloads (~4,000-token contexts) as an agent-style multi-turn loop and interleaves the two tiers on the same growing conversation, so prompt-cache behavior is directly observable per request:

AGENTX_INPUTS=~/projects/agentx-experiments/results/modal2-synth-c128/inputs.json \
  SESSIONS=3 TURNS=3 node agentx-cache-bench.mjs

Measured on GLM-5.3-Flash (Nov 2026, Doubleword), read multiplier Γ—0.2, min prefix 1,024 tok:

Question Measured answer
Do sync and async share one cache? Yes. Both tiers scored hits on the identical interleaved prefix, in both directions (realtime read flex's writes and vice versa). Cache is a property of the deployment, not the tier.
What does a warm hit save? Cached prefix tokens are billed at Γ—0.2. Realized request-cost reduction scales with the hit rate: βˆ’40% to βˆ’53% when hits landed on ~80–96% of prompt tokens; βˆ’8% during a low-hit run.
Are hits guaranteed? No. Realized hit rate swung 0–96% between runs identical in construction β€” consistent with the 5-minute write window plus routing variance across serving workers. Treat cache as opportunistic; agents active within 5-minute bursts benefit most.
Does async change the cache? Hit pattern ~identical across tiers (one run landed on exactly 15.7% hit rate for both). The async discount (βˆ’24/βˆ’27% rates) isindependent β€” the two discounts multiply rather than compete.
Can you see cache writes? Not on this endpoint: cache_creation_input_tokens stays 0; writes are only observable via later reads.

Note: the script prints a warning when "cold" turns already hit cache (state persisted from a recent run) β€” cold baseline numbers are then optimistic.

Thing What it does
/doubleword-refresh Re-pull the catalog from /v1/models?include=pricing now
/doubleword-mode (no arg) Show current tier
/doubleword-mode async|realtime Switch tier for this session (rebinds live)
DOUBLEWORD_EXECUTION_MODE Default tier: async (default) orrealtime
DOUBLEWORD_BASE_URL Override API base (for gateways/proxies; default https://api.doubleword.ai/v1 ).Note: a custom value authorizes sending the configured Doubleword credential to that endpoint β€” set it only for hosts you trust
DOUBLEWORD_CONTEXT_WINDOW Fallback context window (131072) if the catalog doesn't say
DOUBLEWORD_MAX_OUTPUT_TOKENS Fallback output limit (16384)

Startup order of auth: DOUBLEWORD_API_KEY env β†’ auth.json (what /login writes) β†’ models.json apiKey (literal or $ENV_NAME). Keys in the form !command are left to pi's own credential machinery.

What the extension does not do: it never overrides pi's /login or per-provider key handling; it only supplements the model list. If a session starts before the catalog loads (offline start), the extension re-registers the fresh catalog and rebinds the current model on the next refresh β€” no pi restart needed.

  • "NONE" or fabricated skills on the skill check β†’ system prompt isn't arriving. Confirmcompat: { supportsDeveloperRole: false } is in the model stanza (or file an issue against your provider/gateway if you're not extending pi).
  • Manifest 401 on startup β†’ run/doubleword-refresh after fixing auth; never restart pi for it.
  • Qwen or another model sends you to a 400 β†’ that model rejects thedeveloper role; your client (pi, gateway) needssupportsDeveloperRole: false / role normalization.
  • Tier flips don't change the bill β†’ confirm with the probe or a proxy, and remember/doubleword-mode isper-session ; exportDOUBLEWORD_EXECUTION_MODE for persistent default.
  • Costs look wrong β†’ they come from Doubleword's owntariffs array; if they've changed prices,/doubleword-refresh re-fetches.

Files: doubleword.ts (the extension), dev-role-probe.mjs (instruction-delivery probe), flex-bench.mjs (tier cost/latency bench), agentx-cache-bench.mjs (prompt-cache Γ— tier bench). Flex pricing and bench figures in this post used GLM-5.3-Flash figures from November 2026 β€” check /doubleword-mode output or the catalog for current numbers.

── more in #ai-agents 4 stories Β· sorted by recency
── more on @doubleword 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain β€” perfect for shipping the agent you just read about.

$git push zahid main
β†’ Live at https://your-agent.zahid.host βœ“
Get free account β†’ Pricing
from €0/mo Β· no card required
LIVE [news/show-hn-the-price-of…] indexed:0 read:11min 2026-11-13 Β· β€”