Claude¶
Claude Code discovers its context purely from the filesystem, so agedum injects files at the paths Claude already reads and appends nothing to your command. This is the reference harness: pure binds, each scope at its own native location.
Wrapper resolution¶
agedum --wrapper claude -- claude … injects:
| Source | Injected at |
|---|---|
project AGENTS.md |
<root>/CLAUDE.md |
project .agents/skills/ |
<root>/.claude/skills/ |
global ~/.config/agents/AGENTS.md (+ optional AGENTS.claude.md overlay) |
$CLAUDE_CONFIG_DIR/CLAUDE.md (default ~/.claude/CLAUDE.md) |
global ~/.config/agents/skills/ |
$CLAUDE_CONFIG_DIR/skills/ (default ~/.claude/skills/) |
global ~/.config/agents/claude/settings.json (optional overlay) |
$CLAUDE_CONFIG_DIR/settings.json — read-only |
global ~/.config/agents/claude/scripts/ (optional overlay) |
$CLAUDE_CONFIG_DIR/scripts/ — read-only |
- Each scope lands at its own location — never concatenated. The project
CLAUDE.mdcarries only project instructions; the userCLAUDE.mdonly the global ones. Claude reads both and applies its own precedence. See Scopes. - The global
CLAUDE.mdis the base~/.config/agents/AGENTS.mdwith an optionalAGENTS.claude.mdoverlay appended (user scope only; the projectCLAUDE.mdtakes no overlay). See per-harness overlay. - For each skill, the base
SKILL.mdis merged with an optionalSKILL.claude.mdoverlay (front-matter union with overlay winning, bodies concatenated), then task files and scripts are copied verbatim. - The user config dir gets up to four global overlays:
CLAUDE.md,skills/, and — when aclaude/overlay is present in the source root — the read-onlysettings.json+scripts/(below). Your~/.claude.jsonauth and anything else in the dir are untouched. extra_args: none. The command runs exactly as you wrote it.
# Interactive Claude with project + global context injected:
agedum --wrapper claude -- claude
# Headless review with a specific model:
agedum --wrapper claude -- claude --model sonnet -p "review this change"
Claude overlay — settings.json + hook scripts¶
settings.json (permissions, hooks, statusline) and the hook scripts/ it calls are
Claude-specific config, not agent-neutral source, so they live in a claude/ corner of the
global source root — ~/.config/agents/claude/settings.json and
~/.config/agents/claude/scripts/. At global scope agedum binds each read-only into
the user config dir ($CLAUDE_CONFIG_DIR/settings.json + /scripts/), gated on the source
existing: a host with no claude/ overlay gets no binds and nothing changes.
Two consequences of the read-only bind, both by design:
- agedum never writes into
~/.claude/— it bind-mounts, exactly as forCLAUDE.mdand skills. That is why the overlay is sourced from the writable~/.config/agents/root rather than being copied straight into~/.claude/: under a sandbox launch (and some managed environments)~/.claude/is mounted read-only, and a writer that targets it fails withEROFS. - Claude never rewrites user-scope
settings.jsonin place — change it at the source and re-inject. Session-level permission grants still land in the project.claude/settings.local.json(a separate, untracked layer), so this does not get in the way.
The overlay files are typically shipped by agentsconf;
agedum only consumes whatever has landed under ~/.config/agents/claude/.
Provider config¶
A provider config repoints Claude Code at a custom endpoint, model, and
auth. Native Claude (the real Anthropic API, your own login) needs no provider — just
run claude; a provider is only for overriding the endpoint/model/auth.
{
"harness": "claude",
"slug": "claude-deepseek",
"secretEnv": "DEEPSEEK_API_KEY",
"requiredEnv": ["DEEPSEEK_API_KEY"],
"config": {
"baseUrl": "https://api.deepseek.com/anthropic",
"model": "deepseek-v4-pro",
"foldSystemMessages": true
}
}
When baseUrl is empty the harness runs bare (no provider overrides). Otherwise the
config block maps to environment variables:
config key |
Set as |
|---|---|
baseUrl |
ANTHROPIC_BASE_URL |
authStyle + secretEnv |
ANTHROPIC_AUTH_TOKEN (bearer, default) or ANTHROPIC_API_KEY (apikey); the other is unset |
model |
ANTHROPIC_MODEL |
smallFastModel |
ANTHROPIC_SMALL_FAST_MODEL |
haikuAlias / sonnetAlias / opusAlias |
ANTHROPIC_DEFAULT_{HAIKU,SONNET,OPUS}_MODEL |
subagentModel |
CLAUDE_CODE_SUBAGENT_MODEL |
maxContextTokens (> 0) |
CLAUDE_CODE_MAX_CONTEXT_TOKENS |
effortLevel |
CLAUDE_CODE_EFFORT_LEVEL |
disableCaching |
DISABLE_PROMPT_CACHING=1 |
disable1M |
CLAUDE_CODE_DISABLE_1M_CONTEXT=1 |
disableAdaptiveThinking |
CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING=1 |
disableTelemetry |
DISABLE_TELEMETRY=1 |
disableErrorReporting |
DISABLE_ERROR_REPORTING=1 |
disableClaudeApiSkill |
CLAUDE_CODE_DISABLE_CLAUDE_API_SKILL=1 |
foldSystemMessages |
AGEDUM_FOLD_SYSTEM_MESSAGES=1 — see below |
upstreamApi |
openai-completions → AGEDUM_TRANSLATE_OPENAI=1 (see below); anthropic-messages / unset → no translation |
openaiPromptCacheKey |
AGEDUM_OPENAI_PROMPT_CACHE_KEY=1 — inject a per-launch prompt_cache_key (translate proxy only; see below) |
openaiThinking |
AGEDUM_OPENAI_THINKING=<mode> — "toggle" maps Anthropic thinking on/off (translate proxy only) |
autoCompactWindow (> 0) |
CLAUDE_CODE_AUTO_COMPACT_WINDOW |
extraEnv (object) |
arbitrary env for the claude child, stringified + applied last (escape hatch, e.g. CLAUDE_CODE_MAX_OUTPUT_TOKENS, DISABLE_COMPACT) |
secretEnv(mapped to the auth token) must be present in the env file;baseUrlwithout asecretEnvis an error.upstreamApi: "openai-completions"andfoldSystemMessagesare mutually exclusive (the translator already produces a clean top-levelsystem); setting both is an error.CLAUDE_CODE_USE_{BEDROCK,VERTEX,FOUNDRY,MANTLE}are always unset defensively. Empty strings, zero, andfalseare omitted.- Sizing the context window of a third-party model (verified against Claude Code 2.1.205):
maxContextTokensis honoured only when the model id does not start withclaude-; a first-party id ignores it and keeps Claude Code's own window. It is the knob that lifts a third-party model off the 200,000-token default.autoCompactWindowis clamped to[100_000, 1_000_000], then capped to the context window — the effective threshold ismin(maxContextTokens, autoCompactWindow). SettingmaxContextTokensabove 1,000,000 therefore buys headroom between the compaction trigger and the upstream's hard limit, not a bigger meter.- A model id matching
[1m](e.g. DeepSeek'sdeepseek-v4-pro[1m]) is special-cased to 1,000,000 tokens withoutmaxContextTokens, unlessdisable1Mis set. --prompt/--runseed an interactive vs--printrun — see the prompt-seeding table.
System-role fold proxy¶
Some Anthropic-compatible endpoints (notably DeepSeek's /anthropic) accept only
user / assistant in the messages array and reject a system role with
400 unknown variant 'system'. Claude Code, however, emits hook additionalContext
(e.g. a SessionStart reminder) as a system-role message inside messages, alongside
the genuine top-level system prompt — the real Anthropic API and lenient endpoints
tolerate it; strict ones do not.
Setting foldSystemMessages: true in the config sets AGEDUM_FOLD_SYSTEM_MESSAGES=1. At
run time the claude launch interposes a local 127.0.0.1 reverse proxy in front of
ANTHROPIC_BASE_URL: it folds every system-role message into the top-level system
field (always valid — the endpoint already accepts that field), forwards to the real
upstream, and streams the response back close-delimited (SSE-safe). It serves one request
per connection and treats a client hang-up — an interrupted generation, a reaped idle
socket — as routine, so a dropped peer never surfaces as a stderr traceback. Chunked
request bodies are de-chunked before forwarding, and a hung upstream is bounded by a
generous per-socket-op timeout (300 s — far above the API's streaming ping cadence, so it
only ever fires on a genuinely dead peer). The proxy lives only for the duration of the
wrapped command, and is a no-op for other harnesses and when the flag is unset.
OpenAI-only upstream — translation proxy¶
Claude Code only speaks the Anthropic Messages protocol, but some gateways expose only an
OpenAI /v1/chat/completions surface — or their Anthropic /v1/messages surface is broken
for the traffic Claude Code sends. (OpenCode Go is the motivating case: its /messages
translation drops the tool-function name, so any request carrying tools — which Claude
Code always sends — fails with a 400, while its /chat/completions works.)
Setting upstreamApi: "openai-completions" sets AGEDUM_TRANSLATE_OPENAI=1. At run time the
claude launch interposes a local 127.0.0.1 reverse proxy in front of ANTHROPIC_BASE_URL
that translates between the two protocols, reusing the same threading/lifecycle skeleton
as the fold proxy:
- Request — Anthropic Messages → OpenAI Chat Completions: top-level
systembecomes a leading system message; content blocks,tool_use/tool_result, and images (base64 data URLs) are mapped;tools[].input_schema→function.parameters(rejected JSON-Schemaformatkeywords stripped); the path/v1/messages→/v1/chat/completions; and thex-api-keykey is resent asAuthorization: Bearer. The config'smodeloverrides the request model andeffortLevelis injected asreasoning_effort. WithopenaiPromptCacheKey, a per-launchprompt_cache_keyis added to every request (a prefix-cache routing hint honoured by Moonshot/Kimi); withopenaiThinking: "toggle", Anthropicthinkingis mapped to an on/offthinking: {"type": …}param instead of being dropped. - Response — OpenAI → Anthropic, streaming and non-streaming. A streamed OpenAI SSE
response is re-serialised to the Anthropic event sequence (
message_start→content_block_*→message_delta→message_stop), with tool-call arguments streamed asinput_json_deltafragments. Upstream errors are translated to the Anthropic error envelope with the status preserved, so the real cause still surfaces in Claude Code.
Example config (OpenCode Go, kimi-k2.7-code):
{
"harness": "claude",
"secretEnv": "OPENCODE_GO_API_KEY",
"requiredEnv": ["OPENCODE_GO_API_KEY"],
"config": {
"baseUrl": "https://opencode.ai/zen/go",
"authStyle": "apikey",
"upstreamApi": "openai-completions",
"model": "kimi-k2.7-code",
"effortLevel": "high"
}
}
Scope and limits (v1): the translation covers the subset Claude Code actually emits, not the
full Anthropic API. Per-block cache_control has no OpenAI equivalent and is dropped, but
session-level prefix caching is hinted via openaiPromptCacheKey (above) and OpenAI
cached_tokens are surfaced as Anthropic cache_read_input_tokens. Extended thinking is
dropped by default but can be mapped on/off with openaiThinking: "toggle" (for endpoints that
accept a thinking: {"type": …} param — do not enable it against an always-think model, which
errors on "disabled"). /v1/messages/count_tokens is answered locally with a coarse
chars/4 estimate; input-token usage in message_start is best-effort. --dry-run shows the
interposed translator and its real upstream. The proxy lives only for the duration of the
wrapped command, and is a no-op for other harnesses and when upstreamApi is unset.