Skip to content
AntoninnnnnnnnPublic

About

Claude Code with native SWE-2 subagents, through one local service. Keeps your claude.ai login.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

devinx

Your coding agent keeps your own login. Its subagents run on SWE-2.

Claude Code and Codex both stay signed in as you. The work they delegate goes to Cognition's SWE-2 — Medium, High or Max — through one local service that routes on the model name and nothing else.

One port, no external binary, nothing written into your agent's config.

   devinx --or                                  devinx --cx --or
   └─ Claude Code                               └─ Codex
      claude.ai login                              ChatGPT login
            │                                            │
            └──────────►  devinx.py on :8316  ◄──────────┘
                                 │
                    routed on the model name
                                 │
         ┌───────────────────────┼───────────────────────┐
     claude-*                swe-2-*                  gpt-*
  api.anthropic.com      server.codeium.com    chatgpt.com/backend-api
      relayed                 SWE-2                  relayed

The subagents are real subagents of the client you launched: same tools, same harness, same transcripts. Nothing spawns a second process, and the credential for each upstream is the one its own client sent — devinx holds none of them.

Read this first

This talks to Cognition's private API, not a public one, and it gets there by looking like something it is not:

  • it identifies itself as the Windsurf IDE (IDE_NAME, IDE_VERSION, EXT_VERSION and a matching user-agent in devinx.py);
  • _SYS_REWRITES in devinx.py rewrites parts of the client's system prompt for the specific purpose of getting past Cognition's input classifier, which otherwise rejects them. That is circumvention of a check the provider put there, not a compatibility shim;
  • descriptors/*.fdp are protobuf definitions of that private API, extracted from a published npm package.

It runs on your own Devin/Windsurf subscription and spends your own quota. Using it plausibly breaches Cognition's terms of service. That is your call and your risk, not the authors'.

Not affiliated with, endorsed by, or supported by Anthropic, OpenAI or Cognition.

Install

Requires Python 3.10+, a Devin CLI login for the SWE-2 side, and at least one of the Claude Code CLI or the Codex CLI on PATH.

python3 install.py

It creates a virtualenv, installs two dependencies, drops a devinx launcher in ~/.local/bin, and runs a smoke test that ends with a real SWE-2 call.

If you have not logged into Devin on this machine yet, the installer prints the exact command — the login is interactive and cannot be automated:

XDG_DATA_HOME="<data dir printed by the installer>" devin auth login

Useful flags: --force (overwrite an existing launcher and virtualenv), --port N, --bin DIR, --no-smoke, --global-agents.

What it does and does not touch

Almost nothing is installed into your agent's configuration, because a tool that only works when you ask for it should also be invisible when you don't.

The Claude Code subagents are not written into ~/.claude/agents: the launcher injects them per session with --agents, so a plain session shows no swe2-* agent at all. Installing them globally would list agents that fail when invoked outside devin mode, since nothing routes swe-2 models there. Pass --global-agents for the old behaviour. The orchestrator skill rides along the same way, as a session-scoped --plugin-dir.

Codex is configured entirely through per-session -c overrides, so your config.toml, plugins and agents are untouched. The one exception is the orchestrator skill, which Codex can only load as an installed plugin: the installer writes a devinx.config.toml profile next to your config and materialises the plugin cache. A profile does nothing until it is named, and the launcher names it only for --codex --or.

Use

Claude Code

devinx                      # plain Claude Code, exactly as `claude`
devinx --devin              # with the SWE-2 layer  (--d is a shorthand)
devinx --or                 # ... and the orchestrator skill; implies --devin
devinx --d --resume         # composes with any claude flag
devinx --resume x -p "..."  # flags and prompts pass through untouched

Without a flag, devinx is a plain passthrough: nothing is started, nothing is injected, no proxy.

Codex

devinx --codex              # Codex through devinx  (--cx is a shorthand)
devinx --cx --or            # ... with the SWE-2 roles and the orchestrator skill
devinx --cx exec "..."      # any codex subcommand and flag passes through

--codex implies the service, the same way --devin does. Your root model is whatever your Codex config already says; only what it delegates changes.

Flags at a glance

flag effect
--devin, --d route Claude Code through devinx; inject the swe2-* agents and claude-reviewer
--codex, --cx route Codex through devinx; pin the SWE-2 roles
--or add the orchestrator skill; implies the client flag it is used with
--plain cancel DEVINX_ALWAYS=1 for this one run (an explicit --devin/--or/--codex still wins)
-- everything after it passes through verbatim
--status, --doctor, --explain local diagnostics (see stability controls); recognised in the first position or after devinx's own flags (devinx --d --status), as long as the position is unambiguous
variable effect
DEVINX_ALWAYS=1 make the SWE-2 layer the default
DEVINX_ORCHESTRATOR=1 make the orchestrator the default (implies DEVINX_ALWAYS)
DEVINX_SUBAGENT_MCP=1 give subagents their MCP tools back
DEVINX_PORT change the port for one run
DEVINX_CONTEXT_TOKENS override the context window devinx declares (default 262144)
DEVINX_ALLOW_BROWSER=1 accept browser-originated requests (see below)
DEVINX_DATA override the data directory (logs, lock file, Devin credentials); defaults to the platform data dir
DEVINX_LOG override the log file path; defaults to devinx.log in the data directory
DEVINX_API_KEYS / DEVINX_API_KEY comma-separated Devin credentials (or a single one), instead of every credentials.toml under the data directory
DEVINX_OWNED_PATHS os.pathsep-separated paths/globs the --or hooks enforce as owned files when a subagent's brief declares no <owned-paths> block, and for the main thread (see hooks below); unset means unenforced
DEVINX_DESCRIPTORS an extra directory of protobuf *.fdp descriptors to load, ahead of the packaged ones

--or is separate from the client flags on purpose: the SWE-2 layer changes which model does the work, the orchestrator changes who decides what the work is. A session that just wants cheap subagents does not want a skill telling it to delegate, so --devin alone leaves the skill out entirely.

-d is left alone — it is Claude Code's own --debug. Everything after a bare -- is passed through verbatim, so a prompt containing --devin is never mistaken for the flag.

devinx never adds --dangerously-skip-permissions. Choosing a model and choosing to run tools unattended are separate decisions; pass the flag yourself if you want it. The launcher is deliberately not called cc: on Unix that is the C compiler, which make, autoconf and cgo invoke by name.

Already have your own cc wrapper? Add a branch to it rather than using devinx, and keep whatever flags you already pass:

--devin|--d) exec ~/devinx/.venv/bin/python ~/devinx/launcher.py --devin "$@" ;;
--or)        exec ~/devinx/.venv/bin/python ~/devinx/launcher.py --or "$@" ;;

The service starts on demand the first time and stays up afterwards.

Orchestration

--or turns the session around: your own model stops writing the code and starts running the agents that do.

The trade it is built on is that the two sides are scarce in different ways. Your own context is the thing that runs out fastest; SWE-2 is far cheaper but not free — it enforces a token budget over a rolling window, and a burst of parallel agents empties it, after which calls are refused for one to twelve minutes. devinx reports that refusal as a rate_limit_error with the wait the upstream named, so the client backs off instead of killing the agent.

So execution belongs downstream and judgment stays up top — architecture, decomposition, what the diff actually says, what to tell you.

The roles

agent for writes files
swe2-explorer mapping code, tracing flow, locating symbols and tests no
swe2-worker bounded implementation, targeted fixes, mechanical edits yes
swe2-tester running tests, reproducing failures, adding tests yes
swe2-researcher external docs, version-specific API behaviour no
swe2-reviewer independent read of a finished change no
claude-reviewer (Claude Code only) the same, on your own model, for high-stakes changes no

Every SWE role is pinned to swe-2-max, because it is the strongest — not because it is free. Under rate limiting a weaker tier is a real lever: the same mechanical edit costs less of the shared budget on swe2-medium.

On Claude Code the read-only roles are denied Edit, Write and NotebookEdit outright. Bash can still write, so their prompts say it too — this closes the accidental path, not a determined one.

claude-reviewer is the one agent that costs your own quota: it has no pinned model and inherits the session's own. It is for changes that are expensive to get wrong — security, auth, migrations, data integrity, concurrency — and the skill says so rather than leaving the choice to habit.

How it briefs them

What the skill actually spends its length on is the asymmetry. A strong peer can be handed an outcome; these agents need the approach, the file list, acceptance criteria a command can settle, and tests declared off-limits unless changing them is the job.

And the root reads the real diff before believing any of it: a subagent reporting success is a hypothesis, and the summary is the part that lies. Retry policy is two strikes — narrow the brief once, then take the task back and say that you did.

It is written as defaults with stated escape hatches, not as hard MUST-gates: review is chosen per change, and delegating nothing is a legitimate answer for a two-line fix you have already located.

Topology

                     your own model
                 root — decides and briefs
                            |
        +-------------------+-------------------+
        |                   |                   |
    explorer              worker            researcher
    swe-2-max            swe-2-max           swe-2-max
        |                   |
        +---------+---------+
                  |
               tester
              swe-2-max
                  |
               reviewer
         swe-2-max, or claude-reviewer
          when it is expensive to
              get this wrong
                  |
                  v
                     your own model
          reads the real diff, verifies, reports

The root appears twice on purpose. It is the first thing in the chain and the last, and the bottom half is the half people skip.

Invoking it

On Claude Code the skill is /devinx:swe-orchestrator; on Codex it is swe-orchestrator. Both are also picked up on their own when a task matches.

/devinx:swe-orchestrator

Add rate limiting to the public API.
Map the current middleware chain before touching anything.
One worker per endpoint group, a tester for the limit boundaries
themselves, and review this one with claude-reviewer — a limiter that
is wrong in the permissive direction is not going to announce itself.

Naming the roles is optional; the skill picks them on its own. Saying it is worth doing when you already know the shape of the work, or when you want the expensive reviewer on something that looks routine and is not.

Hooks

--or also loads two Claude Code hooks (plugin/hooks/), the same session-scoped way it loads the skill: --devin alone does not, and neither does Codex, which has no equivalent hook mechanism.

  • edit_loop_guard.py refuses an Edit/Write/NotebookEdit identical to one that has already run and failed three times — the same failing call retried forever instead of the agent reading the file and adjusting. Only real failures count (a call denied by a prompt or another hook never ran); a successful edit, or re-reading the file, forgives them; a new session starts clean.
  • ownership_guard.py refuses an Edit/Write/NotebookEdit outside the files a subagent owns — the wall behind the skill's file ownership, since a prompt alone is a request an agent can talk itself out of. Ownership is declared per agent, in its brief, as an <owned-paths> block (one path or glob per line); the hook reads it back from that agent's own transcript, so parallel workers each get their own boundary. DEVINX_OWNED_PATHS is the session-wide fallback for the main thread and for briefs that declare none.

Both fail open on anything they cannot parse: a hook erroring out must never be the reason a legitimate edit is blocked.

hooks.json's commands resolve python3, then python, then py at invocation time using command -v, which needs a POSIX shell. Untested on Windows: if Claude Code runs hook commands through Git Bash there, this resolves the same way it does on Linux/macOS; if it uses cmd.exe instead, command -v is not a command it understands and the hooks do not run at all. Either way, a missing or unusable interpreter means the hooks silently do not load — not that anything is refused wrongly.

Choosing a tier by hand

Useful without the orchestrator, on Claude Code.

/model offers a single swe-2 entry. It has no tier of its own: the effort slider beside it picks one, which is what makes that slider meaningful.

effort tier
low, medium swe-2-medium
high (default) swe-2-high
xhigh, max swe-2-max

The three tiers are also selectable by name (--model swe-2-high). Asked for explicitly they are never retiered by effort — otherwise a subagent pinned to a tier would follow whatever its parent session was set to. The response reports the tier that actually ran, not the alias.

For a subagent, delegate as usual and pick swe2-medium, swe2-high or swe2-max directly.

Subagents are given disallowedTools: ["mcp__*"]. MCP tool schemas are re-sent in full on every request and there are usually many of them: measured here, a subagent turn drops from ~240KB to ~49KB and a cold first turn from ~58s to a few seconds. It is a glob rather than an allow-list on purpose — every built-in tool stays available, including ones a future release adds, so the agents keep inheriting the native tool set.

Only the subagents are affected; your main session keeps its connectors. Set DEVINX_SUBAGENT_MCP=1 to give them back, or pass --strict-mcp-config to drop MCP for the whole session instead.

How it works

devinx.py listens on 127.0.0.1:8316 and routes on the model name. That single decision is what makes the whole thing possible, and the Codex section below is the story of why.

claude-* and gpt-* are relayed to api.anthropic.com and chatgpt.com/backend-api/codex respectively, untouched, with the credential the client sent. The service holds no credential of its own for either; with none in the request it answers 401 rather than inventing one.

swe-2-* is translated to Cognition's Connect-RPC GetChatMessage. No client header is forwarded there at all: the request is built from scratch with its own header set, so your claude.ai or ChatGPT token cannot reach it.

Speaking the client's own protocol on both sides, rather than translating through a third one in the middle, is what keeps usage accounting exact — input, cache read and cache write map 1:1 — and lets reasoning survive across turns: the client keeps the thinking blocks it receives and replays them, and nothing has to be stored server-side.

Each subagent gets its own conversation key, derived from the session id plus its system prompt and first task. Sharing the parent's key collapses them into a single upstream cascade and costs the prefix cache on every continuation turn.

What it took to make Codex delegate to SWE-2

Codex speaks the Responses API rather than Messages, which is the easy half. The rest was only findable by watching the wire, and three things had to be true at once.

A provider is chosen per session, never per agent. A role's config layer honours model and silently ignores model_provider, so the obvious design — point the subagents at a different provider — cannot work. Routing on the model name instead means one global provider is all Codex needs.

Codex has two multi-agent surfaces. On V2 a spawned agent's task is handed over as ciphertext only OpenAI can open: an executor on any other model receives the envelope and none of the letter. On V1 the same task arrives in the clear. The surface is chosen from the model catalog, so /v1/models serves one pinned to V1, and the launcher disables the V2 feature flag, which resolves ahead of the catalog. That catalog parser is strict and rejects the whole catalog over a single missing key — taking plugins, apps and MCP down with it rather than just the offending row — so every entry spells out every default it wants.

The classifier reads tool descriptions too. Codex's code-mode exec tool ships 16KB of API documentation and is refused whole, while its first ~6KB passes. Since that text changes with every release, the permission_denied retry shrinks tool descriptions rather than replaying the same body. The one other block that had to go is <model_switch>, which Codex injects whenever a turn changes model and which contains the other model's entire system prompt.

With those in place, agents.default_subagent_model puts every spawned agent on swe-2-max, and the roles are the same five the Claude Code side uses.

One difference remains. Codex has no per-agent prompt: everything under [agents] is parsed as a role except a short list of recognised scalars, and an unrecognised one fails config loading outright. An executor brief therefore has nowhere to hang; the roles carry the tier (each -c agents.<role>.description= in launcher.py's CODEX_ROLES), and the orchestrator doctrine reaches the root through the Codex skill in codex/marketplace/, kept in step with the Claude Code one in plugin/skills/.

GPT models in Claude Code

gpt-* sent by Claude Code (on /v1/messages) goes to a local claude-code-proxy sidecar on 127.0.0.1:18765 (DEVINX_GPT_UPSTREAM to change it), which holds its own ChatGPT login and translates Messages to Codex Responses. Claude Code's Anthropic credential is stripped before the hop. Codex's own gpt-* traffic still goes straight to chatgpt.com on /v1/responses.

claude-code-proxy codex auth login          # once; separate from ~/.codex/auth.json
systemctl --user enable --now claude-code-proxy

/model lists Sol (gpt-6.1-sol), Astra (gpt-6-astra) and Luna (gpt-6-luna), with the effort slider (low to max): the launcher passes a session-scoped --settings whose modelPicker rows use behavesAs: claude-opus-5-5 for the client-side handling, and the sidecar turns output_config.effort into Codex's reasoning effort. Medium is the floor: a low turn on the gpt-* route is raised to medium.

Subagents: gpt-{sol,astra,luna}-{medium,high,xhigh,max}, generated by the launcher and injected with the swe2-* ones. The Agent tool takes no effort, so the subagent chosen is the effort chosen (an agent's effort: frontmatter pins it; without one a subagent runs at the session's). Codex's own subagent effort for Sol and Astra is xhigh. A user --settings wins, and then the rows are not added.

Compaction copies Codex (ModelInfo::auto_compact_token_limit in codex-rs): compact at 90% of the 272k window, 244800 tokens, measured on the counts the server reports.

  • Main session: Claude Code compacts it, with modelSettings.<model>. autoCompactWindow at 277800 so that its fixed 33k buffer lands on 244800; behind it the sidecar's Codex-native compaction (CCP_CODEX_SERVER_COMPACTION) keeps the earlier history in model-native form.
  • Subagents (x-claude-code-agent-id), which Claude Code never compacts: devinx does, with the same machinery as SWE-2 but a summariser that runs on the agent's own GPT model through the sidecar, so nothing goes to Cognition. The trigger is the server's last reported usage plus an estimate of what was added since, as in Codex. A summary that takes long keeps the stream alive with SSE comments; a context refusal is retried once, compacted harder, instead of ending the agent. DEVINX_GPT_COMPACT_AT overrides 244800.

The advisor on GPT (off by default). Claude Code declares advisor as a server tool, which on the Anthropic API the server runs and on a GPT model nobody would (the sidecar turns it into a plain function, GPT calls it, and the client answers No such tool available: advisor). Claude Code offers the tool to a GPT model when it treats it as an Opus (the launcher's picker rows say behavesAs: claude-opus-5-5) or when CLAUDE_CODE_ENABLE_EXPERIMENTAL_ADVISOR_TOOL=1 is set, in a GPT main session and in a gpt-* subagent of a Claude one alike. Then devinx answers the call itself: the tool call is held back, a GPT model (gpt-6.1-sol) is given the task and the transcript and asked for advice, and the turn continues upstream with that advice as the tool's result, as one continuous message. The client sees the server_tool_use / advisor_tool_result pair Claude's own advisor leaves, so the exchange is stored and replayed with the conversation; on the way back to the sidecar the pair is turned into an ordinary tool call and result (so a conversation that began on Claude, whose advice is encrypted, also continues on GPT, told that the advice cannot be read). Tool calls made beside the advisor are not run and the model is told so. A failed advisor is an error result, never a failed turn, and a slow one keeps the stream alive with SSE comments. Only streamed requests are handled; for any other the tool is taken out. Requests that do not declare the advisor are relayed byte for byte. The advisor's own calls travel under a cache key derived from the session (<session>:advisor[:<agent>], never the conversation's own, which the sidecar keeps state for): successive advice read ~97% of their transcript from the cache instead of paying for all of it each time. The advisor tool stays in the request until a call is refused, because the tools head the prompt and changing them re-reads the whole history. DEVINX_ADVISOR_MODEL (default gpt-6.1-sol), DEVINX_ADVISOR_EFFORT (high), DEVINX_ADVISOR_MAX_CALLS (0 by default, which turns the feature off and takes the tool out of every gpt-* request; set 3, say, to turn it on, after which that many advice per client request are given and the tool is withdrawn), DEVINX_ADVISOR_TIMEOUT (300s).

Report files. Claude Code tells every subagent "Do NOT Write report/summary/findings/analysis .md files", and a GPT model obeys it even when the task asks for exactly such a file under a path it names (two subagents answered that a generic rule forbade it and sent their findings as messages). On the gpt-* route (subagents only), devinx narrows that sentence to unrequested reports: a file the launching agent asks for, or a path it names, is the deliverable and gets written. The gpt-* subagents' own prompt says the same, for a client that bypasses the rewrite. Claude's own route is relayed untouched, and so is swe-2-* (its subagents were measured writing a requested report with the sentence in place).

Switching a conversation from gpt-* back to claude-* works: GPT-signed thinking (ccp: signatures) is dropped from a claude-* history, and kept unsigned on swe-2-*.

The sidecar is a patched build: upstream claude-code-proxy (0.1.43) accepts browser-originated requests and any Host, so a page could spend the ChatGPT quota. The patch in sidecar/origin-guard.patch refuses any Origin and any non-loopback Host. Upstream's install.sh and brew install the unpatched binary, so build it with devinx instead (needs Rust, via rustup):

python3 install.py --gpt        # or, on its own: python3 sidecar/build.py
~/.local/bin/claude-code-proxy codex auth login   # once, the sidecar's own login

sidecar/build.py checks out the pinned upstream commit, applies the patch, runs cargo build --release --locked, installs the binary in ~/.local/bin (an unpatched one found there is kept as .unpatched) and, on Linux, installs and starts the systemd user unit sidecar/claude-code-proxy.service (port 18765, Codex-native compaction). A sidecar already running is not restarted, since that cuts its GPT turns: pass --restart, or restart it when idle. A unit edited by hand is left alone unless --force.

Why the SWE-2 route refuses browsers

It rejects any request carrying an Origin header, or a Host that is not loopback. That route spends your quota with a credential the service holds, so it cannot make the caller prove anything — and a page you merely visit could otherwise POST to it, since a JSON body sent as text/plain needs no CORS preflight and the attacker never has to read the reply. Browsers send Origin on such a request and API clients do not.

Set DEVINX_ALLOW_BROWSER=1 if you are deliberately driving it from a local web UI. This is not isolation between users of the machine: anything running as you can still reach the port.

Dashboard

http://127.0.0.1:8316/dashboard

Publishing it — a tailnet mount, a funnel, any reverse proxy — means pointing that forwarder at DEVINX_DASHBOARD_PORT (8317 by default), never at the API port. The forwarder publishes every route that answers on the port it is given, /v1/messages included, and it rewrites Host to 127.0.0.1 on the way through, so the guard that refuses a non-loopback Host sees a local call and waves it past. No header survives that. The dashboard port answers only /dashboard, /api/stats and /api/hello, and opens only when a token exists — from DEVINX_DASHBOARD_TOKEN, or from dashboard-token in the data directory so it survives a restart the launcher does.

Set DEVINX_DASHBOARD_TOKEN and the dashboard becomes …/dashboard?k=<token>, for every caller including loopback: judging by the Host header stops a rebound browser and nothing else, since any forwarder — socat, ssh -L, a tailnet mount — delivers its connections from 127.0.0.1 with whatever Host the client chose. A browser pays the token once; the reply sets an HttpOnly cookie and the page scrubs the token back out of the address bar. Without a token set, the dashboard stays loopback-only.

Served by the service itself, so it reads the live log rather than a snapshot: turns, tokens and what the cache saved, latency by percentile, the two rate-limit counters and what each credential is carrying, how far compaction is reducing a conversation before it goes upstream, and the incidents worth watching — with the last non-routine log lines streaming underneath. GET /api/stats returns the same figures as JSON. The log is re-read on demand and cached for a few seconds, so several open tabs cost one pass.

Local diagnostics and stability controls

devinx --status, devinx --doctor --json, and devinx --explain --cx --or inspect the running service without starting it or spending inference quota. The dashboard also shows current requests and their phase. See stability controls and validation for the new resource bounds, opt-in strict compaction, shutdown behaviour, test coverage and limits.

Troubleshooting

Logs are in devinx.log, in the data directory printed by the installer (%LOCALAPPDATA%\devinx, ~/Library/Application Support/devinx, or $XDG_DATA_HOME/devinx). Each request logs a line: route=swe, route=claude or route=codex, with the model and the status.

SWE-2 calls fail, the rest still works. Expected when the Devin credential is missing or expired — the service starts anyway so the main session is unaffected. Log in again with the command above; the running service drops its cached credential when the upstream rejects it, so it picks the new one up without a restart.

The launcher reports the service failed to start. Read devinx.log. A port conflict on 8316 is the usual cause; --port at install time changes it.

After a pull, is the running service the new code? It is checked for you. The service outlives the sessions that use it on purpose — several share it, and closing one should not restart it for the others — so a pull otherwise leaves last week's code answering today's launcher, silently. Each start now stamps a fingerprint of its own source, and the launcher compares it against the file on disk: a stale service is replaced when idle, and reported rather than cut in half when a turn is in flight. curl 127.0.0.1:8316/api/hello shows the build, pid and how many requests are running right now.

An agent stops with a rate-limit error. It should no longer get that far. SWE-2 enforces a token budget over a rolling window, and a burst of parallel agents empties it — measured on one machine, 1107 refusals against 8598 successful turns, in bursts of up to 133 in a row, each naming a wait of one to twelve minutes.

devinx now holds the turn instead of handing the refusal back: it waits the delay the upstream named, with jitter so a fan-out of agents does not all return together and empty the window again, then retries. The client sees a request that took longer; the agent never stops, and the orchestrator is never told anything happened. Measured: a subagent refused with "reset in 1 minute" waited 65 seconds inside one request and finished its task, its parent reporting no error at all.

More than one Devin account. Both of Cognition's limits are per credential — a short one that recycles in well under a minute, and a longer window that can lock for twelve minutes — so a second account is a switch rather than a wait. Log in again with the data directory pointed somewhere else and devinx finds it:

XDG_DATA_HOME="<data dir>/account2" devin auth login

Every credentials.toml under the data directory is loaded, and turns are spread across them round robin — always starting at the same one keeps that one permanently at its ceiling while the others idle, which is the difference between switching on every burst and not being limited at all. On a rate limit the turn moves to another credential immediately, and only waits when every one of them is spent. DEVINX_API_KEYS takes a comma-separated list instead. Each request logs the account that served it, so which credential a limit belongs to is answerable from the log. A login done while the service is running is picked up without a restart, within 30 seconds (DEVINX_RESCAN); deleting a credential's file removes it the same way.

DEVINX_RATE_WAIT bounds the total wait (1800s by default) — the client has its own timeout, and an answer that never comes is worse than one that says to try later. Past that budget the refusal does go back, as a 429 rate_limit_error carrying retry-after.

A SWE-2 turn ends with permission_denied. Cognition's input classifier refused the payload. The log says which attempt failed and what was capped; the retry shrinks tool descriptions on its own.

A subagent dies with prompt is too long instead of compacting. Three things have to be right, and the one that actually killed agents was none of the obvious two.

The client enforces the declared window itself for a model it does not recognise, and for a subagent that enforcement is fatal rather than corrective: the agent ends with "Agent terminated early due to an API error: Prompt is too long (error type invalid_request)" and devinx never receives a request at all — refused on the client's own estimate, without compacting and without asking. The launcher therefore sets CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT, measured: the same subagent then runs to completion, its turns reaching the upstream that was always willing to take them. DEVINX_ENFORCE_WINDOW=1 puts the local cap back.

The other two are the window and the wording.

And past those, the proxy compacts. A client compacts its main session but not an agent: measured, an agent at its limit drops a single message — 30412 tokens to 30284 — and then ends with that error, having summarised nothing. So devinx does it instead. When a turn would not fit, the first turn is kept (it is the task), the recent turns are kept verbatim, and everything between is replaced by a summary written with Claude Code's own compaction prompt, read out of its binary rather than reinvented. The client's transcript is untouched; only what goes upstream is reduced, which the agent experiences as a turn that took a few seconds longer. Measured end to end: a subagent whose context reached 158k against an 80k threshold compacted to 18k and finished its task.

Compaction is lossy by nature — the agent keeps the summary, not the detail — and a threshold set below the size of what the agent is actually working on will make it re-read the same files forever. DEVINX_COMPACT_AT sets it; the default leaves roughly 96k of recent turns verbatim. devinx declares 262144 — the real one — through CLAUDE_CODE_MAX_CONTEXT_TOKENS, which is the only lever that works: a context_window on the model catalog is ignored for a model the client does not recognise. DEVINX_CONTEXT_TOKENS overrides it, and erring low costs only an earlier compaction.

The other is the wording of the refusal. A client recovers from an overflow by parsing two numbers out of the message, and Cognition's has none in it — it says "The prompt is too long for this model" and nothing more, so the recovery never fires. devinx rewrites it as prompt is too long: N tokens > M maximum, with N estimated locally and M the declared window. Measured on one machine before that change: 48 overflows in a day, on requests of 1.7MB and 200 messages, while every turn that did compact stopped at 257k — the window was being honoured on one path and ignored on another.

Checking what a Codex session will actually see, without spending a request:

devinx --cx --or debug prompt-input

Everything is broken. claude and codex on their own do not go through devinx at all and are always available as a fallback.

Refreshing the protobuf descriptors

descriptors/*.fdp are Cognition's message definitions, shipped with the package. They only need refreshing if the upstream wire format changes:

python3 extract_fdps.py descriptors

It resolves the latest published catalog, so it can pull a version newer than the one this release was tested against.

About

Claude Code with native SWE-2 subagents, through one local service. Keeps your claude.ai login.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages