Files
dtzp555-maxGitHubtaodengClaude <claude-opus-4-8> <noreply@anthropic.com>
fe12419386 feat(server): SPOT-derived prompt budget — MAX_PROMPT_CHARS follows models.json (ADR 0009) (#179)
* feat(server): SPOT-derived prompt budget — MAX_PROMPT_CHARS default follows models.json (ADR 0009)

Maintainer directive (2026-07-18): the hand-set 150,000-char default (~37.5k English
tokens) is obsolete in the long-context era. Instead of a new constant that would rot
the same way, the default now derives from the SPOT:

  MAX_PROMPT_CHARS (default) = max(models.json contextWindow) x 3 chars/token
                             = 200000 x 3 = 600,000 chars today (~150-200k tokens)

x3 is the CJK-safe multiplier: English runs ~4 chars/token, CJK ~1-1.5, so a
1M-token-derived char cap would let CJK text sail past the model's real window into
an upstream rejection; at x3 the cap fires at roughly the model's true window and OCP
truncates gracefully (tail-first) instead. Pure derivePromptCharBudget() in
lib/prompt.mjs with a 150k floor guarding degenerate SPOT states (empty models[],
absent contextWindow) - a zero budget would truncate every request to nothing.

CLAUDE_MAX_PROMPT_CHARS (env) and the runtime settings API remain ABSOLUTE overrides;
derivation applies only when neither is set. If models.json ever advertises a larger
window (e.g. 1M for the 1M-native models), the budget scales automatically - that
advertisement is a separate deliberate decision (quota burn, OpenClaw compaction, TUI
paste limits) explicitly NOT made here; see ADR 0009.

Behavior change (intended): requests between 150k and 600k chars previously truncated
now pass through whole - longer TTFT + higher quota use for those requests. Truncation
mechanism/logging unchanged; only the default's provenance changed.

ALIGNMENT.md Rule 2: no cli.js citation applies - the truncation guard is OCP-internal
prompt shaping; no endpoint, header, or wire field changes.

Tests: +4, doubly mutation-proven (max->min fails the largest-window test; dropping
the floor fails the floor test). Suite 347 passed / 0 failed. ADR 0009 + index row +
README env-table row included (release_kit: env var default change documented).

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

* fix(server): empty CLAUDE_MAX_PROMPT_CHARS falls back to derived default (PR #179 review)

Reviewer regression: `!= null` treated an EMPTY env value ("CLAUDE_MAX_PROMPT_CHARS="
in an EnvironmentFile/.env) as explicit -> parseInt("") = NaN -> guard disabled + a
false "[System] Note: 0 older messages were truncated" injected into every prompt.
Extracted resolvePromptCharBudget() (truthiness contract, matching the old
`parseInt(env || default)` behavior) into lib/prompt.mjs so the semantics are
mutation-tested: switching back to != null fails the empty-string test. +2 tests, 349/0.

Co-Authored-By: Claude <claude-opus-4-8> <noreply@anthropic.com>

---------

Co-authored-by: dtzp555 <dtzp555@gmail.com>
Co-authored-by: Claude <claude-opus-4-8> <noreply@anthropic.com>
2026-07-18 10:11:38 +10:00
..

Architecture Decision Records

This directory holds the OCP Architecture Decision Records (ADRs) — short documents that capture the why behind structural choices.

Read these before proposing governance, SPOT (single-source-of-truth), or process changes.

Numbering

ADRs start at 0002. The first one (0001) was reserved for an early internal proposal that was superseded before publication; 0002 is deliberately the first published record so the archived 0001 slot remains a placeholder rather than being silently renumbered.

New ADRs increment from the highest existing number. Filenames are NNNN-<short-slug>.md.

Index

ADR Title What it covers
0002 Alignment Constitution The ALIGNMENT.md constitution: why every server.mjs change requires cli.js citation + independent reviewer + CI blacklist pass. Background: the 2026-04-11 drift incident.
0003 models.json as SPOT Why model IDs / aliases / context windows live in a single JSON file (not duplicated in server.mjs and setup.mjs arrays). v3.11.0 refactor.
0004 OpenClaw Auto-Sync Why scripts/sync-openclaw.mjs runs on ocp update, what its scope boundary is (writes only models.providers["claude-local"].models and agents.defaults.models["claude-local/*"]), and the idempotency contract.
0005 No Multi-Provider Why OCP stays single-provider (Anthropic-via-cli.js) and does not extend to OpenAI / Gemini / OpenRouter. Cost estimate: ~7 weeks for a v1 that buys neither moat nor commercial readiness. Separate commercial work starts in a separate repo.
0006 OpenAI Shim Scope The Class A / Class B taxonomy. Class A endpoints (cli.js-mirror) keep Rules 15 verbatim; Class B endpoints (OCP-owned compatibility surface — /v1/chat/completions, /v1/models, admin endpoints) are anchored to OpenAI's spec (B.1) or to an authorizing ADR (B.2). Triggered by PR #99 (external response_format honoring). Grandfathers the existing B.2 inventory at v3.16.4.
0007 TUI Interactive Mode Why TUI-mode spawns an interactive claude in a tmux pane (no -p) to reach the subscription billing pool (cc_entrypoint=cli) rather than the metered Agent SDK pool. Owns the TUI spawn machinery: entrypoint labeling, credential-isolated home, MCP hard-disable, session namespace + defunct-session reaping, the independent concurrency bound, and the /health tui block. Single-user only — hard FATAL on multi-user configs.
0008 TUI Warm Pane Pool Why OCP_TUI_POOL_SIZE pre-boots single-use claude panes (one turn each, own --session-id) — and why reuse is forbidden (transcript.mjs returns the last assistant entry in the file, so a reused session leaks the earlier turn's text). Measured 41% end-to-end. Defines the pool↔reaper invariant (exemption by exact name from a live registry; drain before every sweep so kill-server zombie reaping survives) and the standing idle-process cost. Extends ADR 0007.
0009 SPOT-Derived Prompt Budget Why MAX_PROMPT_CHARS's default is max(models.json contextWindow) × 3 chars/token (600k chars today) instead of a hand-set constant — the old 150k silently under-delivered the advertised window ~5×. ×3 is the CJK-safe multiplier; env/settings stay absolute overrides; whether to advertise 1M windows is explicitly a separate decision.

When to write a new ADR

Open one whenever:

  • A structural rule is being added or changed (e.g., new SPOT, new boundary, new CI guardrail).
  • A decision encodes a lesson from an incident or drift.
  • A future contributor reading the code alone could plausibly undo or re-litigate the choice.

Skip ADRs for routine implementation choices (algorithm pick, naming) — those belong in commit messages.

Format

Keep ADRs short — Context / Decision / Consequences is the standard skeleton. Cite incidents, PRs, or commits where useful.