Catalog PyPI
PACKAGE EXPLORERView in registry

agentroles

PyPI

Tool-agnostic role-to-model mapping for agentic coding workflows

agenticcodingconfigllmmulti-modelrouting
Versions collected1
Direct dependencies5From release 0.1.0
Last observed20 Oct, 23:13 UTC

README

Version 0.1.0 documentation

agentroles

"The difference between a good tool and a great tool is how much thinking it saves you for free." — borrowed from a conversation with a colleague who was tired of editing five config files every time we swapped a model

agentroles workflow diagram

Here's the problem: every single agentic coding tool (OpenCode, Claude Code, Aider, LiteLLM) lets you assign different models to different roles. That's table stakes now. The real friction is that they each invented their own config dialect. OpenCode does provider/model-id in JSON. Claude Code does model: sonnet in YAML frontmatter. Aider does --editor-model flags and a .aider.conf.yml. None of them talk to each other.

So you end up hand maintaining the same "planner uses Opus, implementer uses Haiku" logic in three or four places. You push a model change to OpenCode, forget Claude Code, and suddenly your review agent is running on a model you didn't intend. This isn't speculation. This is what happens in practice when you have more than one tool in your workflow.

agentroles is a single YAML file where you describe which model handles which role, and then it generates the native configs for every tool you use. You never hand write opencode.json agent blocks or Claude Code subagent frontmatter again. Delete agentroles entirely and every file it generated keeps working. It's a config compiler, not a runtime you have to keep alive.

If you're impatient

pip install agentroles
agentroles init

That asks which tools you use, drops a reasonable agentroles.yaml into your project root, and immediately generates all the tool specific configs. If you like what you see, agentroles build regenerates them on demand. agentroles validate checks for the common gotchas like assigning a non Anthropic model to a Claude Code subagent.

What you get out:

ToolFile GeneratedWhat it does
OpenCodeopencode.jsonPer role agent block with full provider/model-id strings
Claude Code.claude/agents/*.mdOne subagent file per role, with correct YAML frontmatter
Aider.aider.conf.ymlmodel, editor-model, weak-model mapped from planner/implementer/summarizer
LiteLLM proxylitellm-config.yamlPer role model_name entries with fallbacks and role metadata tags

The one YAML file

version: 1

roles:
  planner:
    primary: anthropic/claude-opus-4-8
    fallback: [openai/gpt-5.5]
    max_cost_per_call_usd: 0.50
    notes: "Design decisions, ambiguity resolution. Worth paying for quality here."

  implementer:
    primary: anthropic/claude-haiku-4-5
    fallback: [openai/gpt-4o-mini]
    max_cost_per_call_usd: 0.02

  reviewer:
    primary: openai/gpt-5.5

routing:
  mode: static

observability:
  cost_tracking: true
  tag_calls_with_role: true

targets:

  - opencode: ./opencode.json
  - claude_code: ./.claude/agents/
  - aider: ./.aider.conf.yml
  - litellm_proxy: ./litellm-config.yaml

That's it. Roles can have fallbacks, cost ceilings, and notes. The routing block defaults to static (just generate configs) with an opt in dynamic mode if you want runtime escalation. Everything is optional except version and roles.

Schema reference

Top level

FieldTypeRequiredNotes
versionintegeryesOnly 1 exists today. The migrate command exists for when we add v2.
rolesmappingyesOne entry per role name. Must have at least one.
routingobjectnoStatic by default.
observabilityobjectnoCost tracking and metadata tagging.
targetslistnoWhich tools to generate configs for and where to put the output.

Per role

FieldTypeRequiredNotes
primarystringyesModel in provider/model-id format. anthropic/claude-sonnet-5, openai/gpt-5.5, deepseek/deepseek-coder, etc.
fallbackstring[]noOrdered list. The LiteLLM proxy target wires these into native fallback chains.
max_cost_per_call_usdnumbernoPer call cost ceiling. The LiteLLM proxy enforces this.
notesstringnoFree text. Gets threaded into generated agent descriptions where the target tool supports it.

Routing

FieldTypeDefaultNotes
routing.modestringstaticstatic (generate configs only) or dynamic (enable runtime escalation).
routing.dynamic.enabledbooleanfalseMust be explicitly turned on.
routing.dynamic.escalate_onstring[][test_failure, low_confidence]Which concrete signals trigger moving to the next fallback model.
routing.dynamic.cache_awarebooleantrueSuppress escalation if switching providers would blow the KV cache and the signal isn't severe enough to justify it.

Observability

FieldTypeDefaultNotes
observability.cost_trackingbooleantrueEnables cost tracking callbacks in the LiteLLM proxy config.
observability.tag_calls_with_rolebooleantrueAdds role= metadata to every outgoing request. Lets you slice cost dashboards by role.

What each target supports

Tools aren't equal in what they let you configure. This table is honest about the gaps.

OpenCodeClaude CodeAiderLiteLLM proxy
Per role static model assignmentFullFull, Anthropic only via model: field3 slots only (planner, implementer, summarizer)Full, any provider
Cross provider modelsAny providerOnly via model: inherit + LiteLLM proxy routingAny providerAny provider
Dynamic escalationVia opt in routing modeNot supported nativelyNot supportedVia router_settings block
Fallback chainsNot generated nativelyNot generated nativelyNot supportedFull native fallback chains
Cost ceilingsNot enforced by OpenCodeNot enforced by Claude CodeNot enforced by AiderEnforced via max_cost_per_call_usd
Role metadata taggingNot nativelyNot nativelyNot nativelyFull metadata.role tagging

Claude Code: the Anthropic constraint explained

Claude Code's model: field in subagent frontmatter only accepts sonnet, opus, haiku, or inherit. That's it. No OpenAI models, no Gemini, no DeepSeek.

If you assign a non Anthropic model to a role and you have claude_code as a target, agentroles generates the file with model: inherit and a clear comment at the top explaining the situation. It tells you to either pick an Anthropic model for that role or route it through the LiteLLM proxy instead. It never silently drops the model or fabricates a mapping that doesn't exist.

Aider: three slots, no more

Aider has exactly three model slots: --model (main/architect), --editor-model (mechanical editor), and --weak-model (summaries and commit messages). We map planner -> model, implementer -> editor-model, summarizer -> weak-model by convention.

If your agentroles.yaml defines more roles than those three, the extras get reported explicitly in the build output: "these roles are not representable in Aider, skipping." You'll see exactly which ones and why. They're not silently dropped.

Routing modes

Static vs dynamic routing in agentroles

Static (the default)

Reads the YAML, generates configs. No runtime process, no network calls, nothing to keep alive. This is where you should start. It's deterministic and you can check the generated files into version control.

Dynamic / escalation (opt in)

When you set routing.mode: dynamic and routing.dynamic.enabled: true, each task starts on the role's primary model and only moves to the next fallback when something concrete happens: a test fails, the model self reports low confidence, or the plan needs revision mid task.

This is signal triggered escalation, not upfront classification. We deliberately avoid trying to guess task difficulty before the model even starts. That approach generalizes poorly on real agentic coding tasks (the ACRouter paper published in 2026 found simple classifiers degrading by 9-21% accuracy out of distribution, sometimes worse than random).

With cache_aware: true, the router suppresses cross provider escalation for low severity signals. Switching from Anthropic to OpenAI mid session dumps your KV cache on both sides. That cost can easily erase whatever savings you hoped to get from using a cheaper fallback. The router only allows the switch when the signal severity justifies it (test failure, plan revision).

The escalation runtime is small, well tested, and completely optional. If you disable it, everything behaves identically to static mode with zero overhead.

CLI commands

agentroles init        # Interactive setup. Picks targets, writes agentroles.yaml, generates configs.
agentroles build       # Regenerates all target configs from an existing agentroles.yaml.
agentroles validate    # Schema lint + target compatibility warnings.
agentroles migrate     # No-op for schema v1. Exists so the schema version field has a purpose.

init accepts -t flags to skip the interactive prompts: agentroles init -t opencode -t aider -y.

build and validate accept -c to point at a non default config path.

npm wrapper

If you'd rather use npx than manage a Python environment:

npx @mohitmishra7/agentroles init

The npm package shells out to the Python package. It checks for Python 3.11+ on your PATH and installs the pip package if missing. This is a convenience wrapper, not a reimplementation. The tradeoff is you still need Python available.

Installation from source

git clone https://GitHub.com/mohitmishra786/agentroles
cd agentroles
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
pytest

License

Apache 2.0. See LICENSE.

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.1.014 Jul 20265

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 23:13 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:b13fdc8281071d72955de810cedd53efbb3294be5055a2e03a52fd21048f9b90