volante 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (155) hide show
  1. volante-0.3.0/.claude-plugin/marketplace.json +14 -0
  2. volante-0.3.0/.editorconfig +24 -0
  3. volante-0.3.0/.env.example +156 -0
  4. volante-0.3.0/.gitattributes +11 -0
  5. volante-0.3.0/.github/ISSUE_TEMPLATE/bug_report.md +42 -0
  6. volante-0.3.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. volante-0.3.0/.github/ISSUE_TEMPLATE/feature_request.md +32 -0
  8. volante-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +18 -0
  9. volante-0.3.0/.github/workflows/ci.yml +72 -0
  10. volante-0.3.0/.github/workflows/publish-mcp.yml +71 -0
  11. volante-0.3.0/.github/workflows/release.yml +195 -0
  12. volante-0.3.0/.gitignore +51 -0
  13. volante-0.3.0/.vscode/extensions.json +10 -0
  14. volante-0.3.0/.vscode/tasks.json +74 -0
  15. volante-0.3.0/CHANGELOG.md +306 -0
  16. volante-0.3.0/CODE_OF_CONDUCT.md +134 -0
  17. volante-0.3.0/CONTRIBUTING.md +54 -0
  18. volante-0.3.0/LICENSE +21 -0
  19. volante-0.3.0/PKG-INFO +788 -0
  20. volante-0.3.0/README.md +752 -0
  21. volante-0.3.0/SECURITY.md +35 -0
  22. volante-0.3.0/demo.py +197 -0
  23. volante-0.3.0/docs/README.md +20 -0
  24. volante-0.3.0/docs/claude-code-live-gate.md +104 -0
  25. volante-0.3.0/docs/p0-p1-review-2026-07-26.md +142 -0
  26. volante-0.3.0/eval/harness.py +693 -0
  27. volante-0.3.0/eval/run.py +147 -0
  28. volante-0.3.0/eval/tasks.py +555 -0
  29. volante-0.3.0/examples/README.md +12 -0
  30. volante-0.3.0/examples/fake_provider.py +121 -0
  31. volante-0.3.0/examples/minimal_library.py +49 -0
  32. volante-0.3.0/mcpb/README.md +61 -0
  33. volante-0.3.0/mcpb/manifest.json +211 -0
  34. volante-0.3.0/plugins/volante/.claude-plugin/plugin.json +13 -0
  35. volante-0.3.0/plugins/volante/.mcp.json +9 -0
  36. volante-0.3.0/plugins/volante/README.md +32 -0
  37. volante-0.3.0/plugins/volante/commands/run.md +13 -0
  38. volante-0.3.0/plugins/volante/skills/orchestrate/SKILL.md +25 -0
  39. volante-0.3.0/pyproject.toml +133 -0
  40. volante-0.3.0/server.json +120 -0
  41. volante-0.3.0/smithery.yaml +208 -0
  42. volante-0.3.0/src/volante/__init__.py +95 -0
  43. volante-0.3.0/src/volante/agent.py +300 -0
  44. volante-0.3.0/src/volante/blackboard.py +35 -0
  45. volante-0.3.0/src/volante/bootstrap.py +879 -0
  46. volante-0.3.0/src/volante/calibrate.py +172 -0
  47. volante-0.3.0/src/volante/cli.py +493 -0
  48. volante-0.3.0/src/volante/compat.py +74 -0
  49. volante-0.3.0/src/volante/cost.py +140 -0
  50. volante-0.3.0/src/volante/inventory.py +448 -0
  51. volante-0.3.0/src/volante/observability.py +234 -0
  52. volante-0.3.0/src/volante/projector.py +131 -0
  53. volante-0.3.0/src/volante/providers/__init__.py +4 -0
  54. volante-0.3.0/src/volante/providers/anthropic.py +261 -0
  55. volante-0.3.0/src/volante/providers/base.py +204 -0
  56. volante-0.3.0/src/volante/providers/claude_code.py +245 -0
  57. volante-0.3.0/src/volante/providers/cli_agent.py +420 -0
  58. volante-0.3.0/src/volante/providers/codex.py +340 -0
  59. volante-0.3.0/src/volante/providers/fake.py +56 -0
  60. volante-0.3.0/src/volante/providers/openai_compat.py +375 -0
  61. volante-0.3.0/src/volante/py.typed +0 -0
  62. volante-0.3.0/src/volante/registry.py +201 -0
  63. volante-0.3.0/src/volante/router.py +929 -0
  64. volante-0.3.0/src/volante/runtime.py +1238 -0
  65. volante-0.3.0/src/volante/supervisor.py +376 -0
  66. volante-0.3.0/src/volante/synthesizer.py +142 -0
  67. volante-0.3.0/src/volante/tools/__init__.py +1 -0
  68. volante-0.3.0/src/volante/tools/base.py +15 -0
  69. volante-0.3.0/src/volante/tools/docker_sandbox.py +108 -0
  70. volante-0.3.0/src/volante/tools/factory.py +33 -0
  71. volante-0.3.0/src/volante/tools/fetch_url.py +76 -0
  72. volante-0.3.0/src/volante/tools/read_file.py +45 -0
  73. volante-0.3.0/src/volante/tools/run_python.py +45 -0
  74. volante-0.3.0/src/volante/tools/sandbox.py +350 -0
  75. volante-0.3.0/src/volante/types.py +264 -0
  76. volante-0.3.0/src/volante/worker.py +44 -0
  77. volante-0.3.0/tests/conftest.py +36 -0
  78. volante-0.3.0/tests/eval/test_agentic_arm.py +155 -0
  79. volante-0.3.0/tests/eval/test_compare_arms.py +24 -0
  80. volante-0.3.0/tests/eval/test_harness.py +726 -0
  81. volante-0.3.0/tests/eval/test_run.py +322 -0
  82. volante-0.3.0/tests/eval/test_run_suite.py +213 -0
  83. volante-0.3.0/tests/eval/test_suite_goals.py +444 -0
  84. volante-0.3.0/tests/eval/test_tasks.py +80 -0
  85. volante-0.3.0/tests/integration/test_agentic_integration.py +37 -0
  86. volante-0.3.0/tests/integration/test_anthropic_integration.py +29 -0
  87. volante-0.3.0/tests/integration/test_docker_sandbox_integration.py +54 -0
  88. volante-0.3.0/tests/integration/test_openai_compat_integration.py +35 -0
  89. volante-0.3.0/tests/mcp/test_run_goal.py +187 -0
  90. volante-0.3.0/tests/mcp/test_server.py +35 -0
  91. volante-0.3.0/tests/providers/fixtures/claude_code_result.2026-07-22.json +10 -0
  92. volante-0.3.0/tests/providers/fixtures/codex_result.2026-07-23-live.jsonl +4 -0
  93. volante-0.3.0/tests/providers/test_anthropic.py +352 -0
  94. volante-0.3.0/tests/providers/test_anthropic_stream.py +130 -0
  95. volante-0.3.0/tests/providers/test_anthropic_tools.py +96 -0
  96. volante-0.3.0/tests/providers/test_base.py +270 -0
  97. volante-0.3.0/tests/providers/test_claude_code.py +418 -0
  98. volante-0.3.0/tests/providers/test_cli_agent.py +575 -0
  99. volante-0.3.0/tests/providers/test_codex.py +579 -0
  100. volante-0.3.0/tests/providers/test_fake.py +64 -0
  101. volante-0.3.0/tests/providers/test_fake_stream.py +46 -0
  102. volante-0.3.0/tests/providers/test_openai_compat.py +485 -0
  103. volante-0.3.0/tests/providers/test_openai_compat_stream.py +176 -0
  104. volante-0.3.0/tests/providers/test_openai_compat_tools.py +146 -0
  105. volante-0.3.0/tests/test_agent.py +482 -0
  106. volante-0.3.0/tests/test_agent_external_tools.py +106 -0
  107. volante-0.3.0/tests/test_blackboard.py +121 -0
  108. volante-0.3.0/tests/test_bootstrap.py +92 -0
  109. volante-0.3.0/tests/test_bootstrap_wiring.py +594 -0
  110. volante-0.3.0/tests/test_calibrate.py +195 -0
  111. volante-0.3.0/tests/test_cli.py +530 -0
  112. volante-0.3.0/tests/test_compat.py +70 -0
  113. volante-0.3.0/tests/test_cost.py +282 -0
  114. volante-0.3.0/tests/test_demo.py +58 -0
  115. volante-0.3.0/tests/test_distribution_contracts.py +298 -0
  116. volante-0.3.0/tests/test_inventory.py +376 -0
  117. volante-0.3.0/tests/test_inventory_bootstrap.py +238 -0
  118. volante-0.3.0/tests/test_observability.py +198 -0
  119. volante-0.3.0/tests/test_projector.py +174 -0
  120. volante-0.3.0/tests/test_registry.py +131 -0
  121. volante-0.3.0/tests/test_registry_seed.py +93 -0
  122. volante-0.3.0/tests/test_router.py +369 -0
  123. volante-0.3.0/tests/test_router_decision.py +532 -0
  124. volante-0.3.0/tests/test_runtime.py +882 -0
  125. volante-0.3.0/tests/test_runtime_agentic.py +150 -0
  126. volante-0.3.0/tests/test_runtime_agentic_reroute.py +357 -0
  127. volante-0.3.0/tests/test_runtime_hardening.py +1188 -0
  128. volante-0.3.0/tests/test_smoke.py +41 -0
  129. volante-0.3.0/tests/test_supervisor.py +788 -0
  130. volante-0.3.0/tests/test_synthesizer.py +247 -0
  131. volante-0.3.0/tests/test_tooling.py +24 -0
  132. volante-0.3.0/tests/test_types.py +410 -0
  133. volante-0.3.0/tests/test_worker.py +235 -0
  134. volante-0.3.0/tests/tools/__init__.py +0 -0
  135. volante-0.3.0/tests/tools/test_base.py +23 -0
  136. volante-0.3.0/tests/tools/test_docker_sandbox.py +177 -0
  137. volante-0.3.0/tests/tools/test_fetch_url.py +117 -0
  138. volante-0.3.0/tests/tools/test_read_file.py +46 -0
  139. volante-0.3.0/tests/tools/test_run_python.py +54 -0
  140. volante-0.3.0/tests/tools/test_sandbox.py +75 -0
  141. volante-0.3.0/tests/tools/test_sandbox_factory.py +169 -0
  142. volante-0.3.0/tests/tools/test_tools_factory.py +31 -0
  143. volante-0.3.0/tests/webui/test_app.py +173 -0
  144. volante-0.3.0/tests/webui/test_runner.py +73 -0
  145. volante-0.3.0/tests/webui/test_webui_server.py +64 -0
  146. volante-0.3.0/uv.lock +1398 -0
  147. volante-0.3.0/volante_mcp/__init__.py +35 -0
  148. volante-0.3.0/volante_mcp/__main__.py +8 -0
  149. volante-0.3.0/volante_mcp/server.py +152 -0
  150. volante-0.3.0/webui/__init__.py +5 -0
  151. volante-0.3.0/webui/__main__.py +3 -0
  152. volante-0.3.0/webui/_demo.py +109 -0
  153. volante-0.3.0/webui/app.py +1171 -0
  154. volante-0.3.0/webui/runner.py +105 -0
  155. volante-0.3.0/webui/server.py +92 -0
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "volante",
3
+ "description": "Volante — a cross-provider multi-model AI orchestration engine for Claude Code (MCP server + /volante:run command).",
4
+ "owner": {
5
+ "name": "ribato22"
6
+ },
7
+ "plugins": [
8
+ {
9
+ "name": "volante",
10
+ "source": "./plugins/volante",
11
+ "description": "Multi-model AI orchestration: plan a task DAG, route each task to the best capable model. Bundles the Volante MCP server (volante_run) plus a /volante:run command."
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,24 @@
1
+ # EditorConfig — consistent formatting across editors (https://editorconfig.org)
2
+ root = true
3
+
4
+ [*]
5
+ charset = utf-8
6
+ end_of_line = lf
7
+ insert_final_newline = true
8
+ trim_trailing_whitespace = true
9
+ indent_style = space
10
+ indent_size = 4
11
+ max_line_length = 100
12
+
13
+ [*.py]
14
+ indent_size = 4
15
+
16
+ [*.{json,jsonc,yml,yaml,toml,md,js,css,html}]
17
+ indent_size = 2
18
+
19
+ [*.md]
20
+ # Preserve hard line breaks in prose docs.
21
+ trim_trailing_whitespace = false
22
+
23
+ [Makefile]
24
+ indent_style = tab
@@ -0,0 +1,156 @@
1
+ # .env.example — copy to .env and fill in. NEVER commit .env (it is gitignored).
2
+ # Volante reads these at runtime via volante.build_providers_from_env().
3
+ # Configure ONE OR MORE models. Every configured model joins the routing inventory.
4
+ # Baseline priority (planner/synth only): Anthropic > OpenAI-compat > Kimi > Ollama.
5
+
6
+ # --- Anthropic (AnthropicProvider) -------------------------------------------
7
+ ANTHROPIC_API_KEY=
8
+ # ANTHROPIC_MODEL=claude-opus-4-8
9
+ # ANTHROPIC_MODELS=claude-opus-4-8,claude-sonnet-4-5 # plural wins over singular
10
+ # ANTHROPIC_NAMES=anthropic/opus,anthropic/sonnet # optional parallel canonical ids
11
+ # ANTHROPIC_STRENGTHS=coding,reasoning,long_context
12
+ # Optional specialized tags raise task_fit so same-tier models rank apart (see README):
13
+ # code: software_engineering, debugging, instruction_following
14
+ # research: research, grounding, long_context
15
+ # write: writing, creativity, instruction_following
16
+ # analyze: analysis, math, long_context
17
+ # Without any of these AND without a quality profile, task_fit is the same for every
18
+ # model, so Volante gives it zero weight instead of a flat constant.
19
+ # ANTHROPIC_CONTEXT=200000
20
+ # ANTHROPIC_MAX_OUTPUT=8192
21
+ # ANTHROPIC_TOOLS=true
22
+ # ANTHROPIC_COST_IN=0.015
23
+ # ANTHROPIC_COST_OUT=0.075
24
+ # ANTHROPIC_TIER=4
25
+
26
+ # --- Generic OpenAI-compatible slot (OpenAICompatProvider) --------------------
27
+ # Works with ANY OpenAI-compatible endpoint (Google AI Studio / Gemini, Groq,
28
+ # OpenRouter, DeepSeek, local vLLM, ...) with a correct model_id, pricing and
29
+ # context window. Only BASE_URL + MODEL are required.
30
+ OPENAI_COMPAT_BASE_URL=
31
+ OPENAI_COMPAT_MODEL=
32
+ # OPENAI_COMPAT_KEY= # optional; "none" placeholder for keyless local endpoints
33
+ # OPENAI_COMPAT_NAME=vendor/model # canonical model_id label (default: openai-compat/<model>)
34
+ # OPENAI_COMPAT_CONTEXT=128000 # context window (standard default)
35
+ # OPENAI_COMPAT_MAX_OUTPUT=8192 # max output tokens (standard default)
36
+ # OPENAI_COMPAT_TOOLS=true # opt in only when endpoint supports function calling
37
+ # OPENAI_COMPAT_STRENGTHS=coding,reasoning
38
+ # OPENAI_COMPAT_COST_IN=0 # USD / 1k input tokens (0 = free-tier; set for paid)
39
+ # OPENAI_COMPAT_COST_OUT=0 # USD / 1k output tokens
40
+ # OPENAI_COMPAT_TIER=3
41
+ #
42
+ # Example — free Google AI Studio Gemini Flash (highest free-tier intelligence):
43
+ # OPENAI_COMPAT_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai/
44
+ # OPENAI_COMPAT_KEY=<ai-studio-key>
45
+ # OPENAI_COMPAT_MODEL=gemini-flash-latest
46
+ # OPENAI_COMPAT_NAME=google/gemini-flash
47
+ #
48
+ # Multiple OpenAI-compatible providers at once — add OPENAI_COMPAT_2_*, _3_*, …
49
+ # (contiguous from 2). Each keeps its own model_id / pricing / context (no reused slot).
50
+ # Example — Gemini (plans + synthesizes) + Groq (cheaper -> parallel workers), both free:
51
+ # OPENAI_COMPAT_COST_OUT=0.01 # make Gemini "pricier" than Groq
52
+ # OPENAI_COMPAT_2_BASE_URL=https://api.groq.com/openai/v1
53
+ # OPENAI_COMPAT_2_KEY=<groq-key>
54
+ # OPENAI_COMPAT_2_MODEL=llama-3.3-70b-versatile
55
+ # OPENAI_COMPAT_2_NAME=groq/llama-3.3
56
+ # OPENAI_COMPAT_2_COST_OUT=0.0006
57
+
58
+ # --- Moonshot / Kimi (OpenAICompatProvider) ----------------------------------
59
+ MOONSHOT_API_KEY=
60
+ MOONSHOT_BASE_URL=https://api.moonshot.ai/v1
61
+ # MOONSHOT_MODEL=kimi-k2-0711-preview
62
+ # MOONSHOT_MODELS=kimi-k2-0711-preview,kimi-k2-turbo-preview
63
+ # MOONSHOT_NAMES=kimi/k2,kimi/k2-turbo
64
+ # MOONSHOT_STRENGTHS=coding,reasoning
65
+ # MOONSHOT_CONTEXT=128000
66
+ # MOONSHOT_MAX_OUTPUT=4096
67
+ # MOONSHOT_TOOLS=true
68
+ # MOONSHOT_COST_IN=0.0012
69
+ # MOONSHOT_COST_OUT=0.0012
70
+ # MOONSHOT_TIER=3
71
+
72
+ # --- Ollama, local & free (OpenAICompatProvider) -----------------------------
73
+ # Run `ollama serve` and `ollama pull llama3.2`, then set:
74
+ # OLLAMA_BASE_URL=http://localhost:11434/v1
75
+ # OLLAMA_MODEL=llama3.2
76
+ # OLLAMA_MODELS=qwen2.5-coder:14b,llama3.2
77
+ # OLLAMA_NAMES=ollama/qwen2.5-coder:14b,ollama/llama3.2
78
+ # OLLAMA_API_KEY=ollama
79
+ # OLLAMA_STRENGTHS=coding,reasoning
80
+ # OLLAMA_CONTEXT=8192
81
+ # OLLAMA_MAX_OUTPUT=2048
82
+ # OLLAMA_TOOLS=false
83
+ # OLLAMA_COST_IN=0
84
+ # OLLAMA_COST_OUT=0
85
+ # OLLAMA_TIER=1
86
+
87
+ # --- Subscription CLI agents (OPT-IN; consume your INTERACTIVE quota) ----------
88
+ # Drives the official headless CLIs you're logged into — no API key. Orchestrating on
89
+ # them DRAWS FROM YOUR INTERACTIVE SUBSCRIPTION QUOTA (not a metered API): a large run —
90
+ # and especially the eval suite — can exhaust the allowance and trigger a mid-run
91
+ # hard-pause. OFF by default and NEVER registered for the eval (include_subscription=False).
92
+ # Requires the CLI installed & logged in AND the *_ENABLED flag below.
93
+ #
94
+ # Claude Code (`claude -p`, subscription/OAuth):
95
+ # CLAUDE_CODE_ENABLED=1
96
+ # CLAUDE_CODE_MODEL=opus # wire model (tier 4 default)
97
+ # CLAUDE_CODE_MODELS=opus,sonnet # plural wins over singular
98
+ # CLAUDE_CODE_TIER=4 # ModelInfo.tier for the claude-code seed
99
+ # CLAUDE_CODE_SYSTEM_PROMPT_MODE=replace # replace (default) | append.
100
+ # "replace" makes `claude -p` a raw completion (the worker/planner persona REPLACES Claude
101
+ # Code's base system prompt). Live-verified: with "append", opus answers the goal instead
102
+ # of emitting the planner's strict JSON DAG -> planning fails. Use "append" only if you
103
+ # deliberately want the task layered on top of Claude Code's own agent behavior.
104
+ # CLAUDE_CODE_TIMEOUT=120 # subprocess timeout (s)
105
+ # CLAUDE_CODE_MAX_OUTPUT=4096 # conservative max output tokens
106
+ #
107
+ # Codex (`codex exec`, ChatGPT subscription; child env scrubs OPENAI/CODEX API keys):
108
+ # CODEX_ENABLED=1
109
+ # CODEX_MODEL=gpt-5-codex # follows your Codex config if unset
110
+ # CODEX_MODELS=gpt-5-codex,gpt-5-codex-mini
111
+ # CODEX_TIER=3 # REQUIRED when enabled (no model-name sniffing)
112
+ # CODEX_CONTEXT=256000 # default when unset (build_codex_model())
113
+ # CODEX_MAX_OUTPUT=4096
114
+ #
115
+ # Per-run subscription-call cap (reroute to a direct/card model when reached):
116
+ # VOLANTE_MAX_SUBSCRIPTION_CALLS=16 # incl. planner preflight/plan/retry/worker/synth
117
+ # VOLANTE_CLI_AGENT_DEPTH=0 # recursion guard (Volante may run INSIDE Claude Code)
118
+ # OPENAI_COMPAT_TIER=3 # per-slot routing tier (1 small … 4 frontier)
119
+
120
+ # Optional strict JSON evidence/metadata keyed by canonical model id. Keep both out of git.
121
+ # Scores are 0..1; task keys: code, research, write, analyze.
122
+ # VOLANTE_QUALITY_PROFILES_FILE=/absolute/path/to/quality-profiles.json
123
+ # Use overrides when models in one plural family have different hard capabilities,
124
+ # context/output limits, prices, or tiers. See the JSON schema/example in README.
125
+ # VOLANTE_MODEL_OVERRIDES_FILE=/absolute/path/to/model-overrides.json
126
+
127
+ # --- Sandbox & tools ---------------------------------------------------------
128
+ # VOLANTE_SANDBOX=docker # real container isolation for agentic run_python
129
+ # # (--network none, read-only root, cgroup limits).
130
+ # Unset = auto + fail closed: Docker is used when its daemon answers; when it does not,
131
+ # run_python is NOT offered at all (the planner is told it cannot execute code) rather
132
+ # than silently running model-written code with your files and network. To accept that
133
+ # weaker sandbox deliberately, opt in with:
134
+ # VOLANTE_SANDBOX=subprocess # no isolation: code runs as you, with host access
135
+ # # (legacy name AIORCH_SANDBOX still works as a fallback)
136
+ # VOLANTE_FETCH_ALLOWLIST=example.com,docs.python.org # enables host-mediated fetch_url
137
+ # VOLANTE_READ_ROOT=/absolute/path/to/trusted/files # enables root-scoped read_file
138
+ # The hostname allowlist is not an SSRF boundary against DNS rebinding/private resolutions,
139
+ # and READ_ROOT should be a trusted tree without adversarial concurrent symlink changes.
140
+
141
+ # --- Web UI (webui/; `uv sync --extra ui` then `uv run python -m webui`) ------
142
+ # VOLANTE_UI_HOST=127.0.0.1
143
+ # VOLANTE_UI_PORT=8000
144
+ # VOLANTE_UI_AUTH_TOKEN= # required before binding beyond loopback
145
+ # VOLANTE_UI_ALLOWED_HOSTS= # comma-separated Host header allowlist for remote access
146
+ # VOLANTE_UI_MAX_GOAL_CHARS=20000
147
+ # VOLANTE_UI_MAX_CONCURRENT_RUNS=2
148
+
149
+ # --- Observability (CLI, MCP server, and Web UI) -----------------------------
150
+ # VOLANTE_LOG=info # raise engine diagnostics to stderr (debug/info/warning/error).
151
+ # # Blank/unset = silent. Use this to see MCP activity in VS Code's
152
+ # # "Output" pane. Never written to stdout (the MCP/pipe channel).
153
+ # VOLANTE_USAGE_LOG=~/.volante/usage.jsonl # append one JSON line per run (status, cash vs plan
154
+ # # credit, subscription calls, duration, models, truncated goal).
155
+ # # This is the ledger behind `volante --usage` and the Web UI "Usage"
156
+ # # page, so IDE/MCP runs stay auditable. Set empty to disable.
@@ -0,0 +1,11 @@
1
+ # Normalize line endings across platforms.
2
+ * text=auto eol=lf
3
+
4
+ # Keep GitHub Linguist's language stats representative of the engine itself:
5
+ # the internal design docs/plans are large but are documentation, not code.
6
+ *.md linguist-documentation
7
+ docs/** linguist-documentation
8
+ uv.lock linguist-generated=true -diff
9
+
10
+ # Treat lockfiles/notebooks as generated in diffs.
11
+ *.ipynb linguist-generated=true
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: Bug report
3
+ about: Report something that isn't working as expected
4
+ title: "[Bug] "
5
+ labels: bug
6
+ assignees: ""
7
+ ---
8
+
9
+ ## Describe the bug
10
+
11
+ A clear, concise description of what the bug is.
12
+
13
+ ## Reproduction
14
+
15
+ Minimal steps to reproduce the behavior, ideally a `uv run volante "..."` invocation, a
16
+ `demo.py`/library snippet, or a failing `uv run pytest` test.
17
+
18
+ ```bash
19
+ # commands / code here
20
+ ```
21
+
22
+ ## Expected behavior
23
+
24
+ What you expected to happen.
25
+
26
+ ## Actual behavior
27
+
28
+ What actually happened (include the full error output / traceback if any — `volante` itself should
29
+ never print a raw traceback, so if you see one that's part of the bug).
30
+
31
+ ## Environment
32
+
33
+ - Volante version (`uv run volante --version`):
34
+ - Python version (`python --version`):
35
+ - OS:
36
+ - Provider(s) configured (Anthropic / OpenAI-compat / Kimi / Ollama / Claude Code / Codex — no need
37
+ to share keys):
38
+
39
+ ## Additional context
40
+
41
+ Anything else relevant (logs, related issues, whether it reproduces with `FakeProvider` / no
42
+ network).
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Ask a question / discuss an idea
4
+ url: https://github.com/ribato22/volante/discussions
5
+ about: For open-ended questions or design discussion before filing a bug/feature issue.
6
+ - name: Report a security vulnerability
7
+ url: https://github.com/ribato22/volante/security/advisories/new
8
+ about: Please do NOT open a public issue for security problems — see SECURITY.md.
@@ -0,0 +1,32 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea or enhancement for Volante
4
+ title: "[Feature] "
5
+ labels: enhancement
6
+ assignees: ""
7
+ ---
8
+
9
+ ## Problem
10
+
11
+ What are you trying to do, and what's missing or awkward today? (Link to the relevant
12
+ [Roadmap](https://github.com/ribato22/volante/blob/main/README.md#roadmap) item if there is one.)
13
+
14
+ ## Proposed solution
15
+
16
+ Describe the change you'd like. If it touches routing, providers, cost accounting, or the eval
17
+ scorer, please be explicit about the exact guarantee/limitation you expect (see
18
+ [SECURITY.md](https://github.com/ribato22/volante/blob/main/SECURITY.md) for the existing threat model).
19
+
20
+ ## Alternatives considered
21
+
22
+ Any alternative approaches or workarounds you've considered.
23
+
24
+ ## Additional context
25
+
26
+ Anything else — related issues, links, sketches of the API/CLI surface.
27
+
28
+ ---
29
+
30
+ Note: Volante is a **study engine**, not a production framework (see the README's
31
+ [Non-goals](https://github.com/ribato22/volante/blob/main/README.md#non-goals) section) — it
32
+ deliberately avoids adopting orchestration frameworks (LangChain/LiteLLM/CrewAI) internally.
@@ -0,0 +1,18 @@
1
+ ## What & why
2
+
3
+ <!-- Describe what changed and why. Note any new invariant or limitation this introduces. -->
4
+
5
+ ## Checklist
6
+
7
+ - [ ] `uv run pytest` is green (zero-network by default; ran `uv run pytest -m integration` too if
8
+ this touches network/Docker/subscription-provider code)
9
+ - [ ] `uv run ruff check .` is clean
10
+ - [ ] Tests added/updated for the behavior change (see
11
+ [CONTRIBUTING.md](https://github.com/ribato22/volante/blob/main/CONTRIBUTING.md) — test-driven,
12
+ small focused changes)
13
+ - [ ] `CHANGELOG.md`'s `[Unreleased]` section is updated (Added/Changed/Fixed, matching existing
14
+ style)
15
+ - [ ] If this touches isolation/sandboxing or the eval scorer: the exact guarantee and its limits
16
+ are stated honestly (see
17
+ [SECURITY.md](https://github.com/ribato22/volante/blob/main/SECURITY.md)) — no overclaiming
18
+ - [ ] README updated if user-facing behavior (CLI flags, env vars, Providers) changed
@@ -0,0 +1,72 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ # Least privilege: CI only needs to read the repo contents.
9
+ permissions:
10
+ contents: read
11
+
12
+ concurrency:
13
+ group: ci-${{ github.ref }}
14
+ cancel-in-progress: true
15
+
16
+ jobs:
17
+ test:
18
+ name: test (py${{ matrix.python-version }})
19
+ runs-on: ubuntu-latest
20
+ strategy:
21
+ fail-fast: false
22
+ matrix:
23
+ python-version: ["3.11", "3.12", "3.13"]
24
+ steps:
25
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
26
+
27
+ - name: Install uv
28
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
29
+ with:
30
+ python-version: ${{ matrix.python-version }}
31
+ enable-cache: true
32
+
33
+ - name: Install dependencies
34
+ run: uv sync --dev --locked
35
+
36
+ - name: Lint (ruff)
37
+ run: uv run ruff check .
38
+
39
+ - name: Type-check (mypy; enforces the shipped py.typed promise)
40
+ run: uv run mypy src/volante
41
+
42
+ - name: Test (no network; integration tests skipped by default)
43
+ run: uv run pytest --cov=src/volante --cov=eval --cov-report=term-missing
44
+
45
+ build:
46
+ name: build & package smoke test
47
+ runs-on: ubuntu-latest
48
+ steps:
49
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
50
+
51
+ - name: Install uv
52
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
53
+ with:
54
+ python-version: "3.12"
55
+ enable-cache: true
56
+
57
+ - name: Build sdist + wheel
58
+ run: uv build
59
+
60
+ - name: Check package metadata (twine)
61
+ run: uvx twine check dist/*
62
+
63
+ - name: Wheel-install smoke test
64
+ # Catches packaging regressions a unit test can't (accidental webui/ or docs/
65
+ # inclusion, a broken `volante` console-script entrypoint, missing dependency
66
+ # metadata) by installing the built wheel into a throwaway venv — separate
67
+ # from the dev environment `uv sync --dev` builds for the test job above.
68
+ run: |
69
+ python3 -m venv /tmp/volante-wheel-venv
70
+ /tmp/volante-wheel-venv/bin/pip install --quiet dist/*.whl
71
+ /tmp/volante-wheel-venv/bin/volante --version
72
+ /tmp/volante-wheel-venv/bin/volante --help
@@ -0,0 +1,71 @@
1
+ name: Publish to MCP Registry
2
+
3
+ # Publishes server.json to the official MCP Registry (registry.modelcontextprotocol.io)
4
+ # via GitHub OIDC — no token stored. The `io.github.ribato22/*` namespace is authorized
5
+ # because this workflow runs in ribato22's repo. The release workflow calls this
6
+ # after PyPI and GitHub Release succeed; maintainers can also run it manually.
7
+ on:
8
+ workflow_call:
9
+ inputs:
10
+ ref:
11
+ description: Immutable release ref to publish
12
+ required: true
13
+ type: string
14
+ workflow_dispatch:
15
+ inputs:
16
+ ref:
17
+ description: Git ref whose server.json should be published
18
+ required: true
19
+ default: main
20
+ type: string
21
+
22
+ permissions:
23
+ id-token: write # OIDC token for MCP Registry auth
24
+ contents: read
25
+
26
+ jobs:
27
+ publish-mcp:
28
+ name: Publish server.json to the MCP Registry
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
32
+ with:
33
+ ref: ${{ inputs.ref }}
34
+
35
+ - name: Wait for the PyPI release to carry the ownership marker
36
+ # The registry verifies ownership by finding `mcp-name: <name>` in the PyPI
37
+ # package description (our README). Ensure the matching version is live first,
38
+ # so a tag that also triggers the PyPI release doesn't race ahead of it.
39
+ run: |
40
+ ver="$(python3 -c "import json;print(json.load(open('server.json'))['version'])")"
41
+ name="$(python3 -c "import json;print(json.load(open('server.json'))['name'])")"
42
+ echo "waiting for PyPI volante $ver with marker 'mcp-name: $name'"
43
+ for i in $(seq 1 40); do
44
+ desc="$(curl -sf "https://pypi.org/pypi/volante/$ver/json" \
45
+ | python3 -c "import sys,json;print(json.load(sys.stdin)['info']['description'])" 2>/dev/null || true)"
46
+ if printf '%s' "$desc" | grep -q "mcp-name: $name"; then
47
+ echo "marker present on PyPI $ver"; exit 0
48
+ fi
49
+ echo "attempt $i/40: not yet; sleeping 20s"; sleep 20
50
+ done
51
+ echo "::error::PyPI $ver with the ownership marker was not found in time"; exit 1
52
+
53
+ - name: Install mcp-publisher
54
+ run: |
55
+ version="1.7.9"
56
+ archive="mcp-publisher_linux_amd64.tar.gz"
57
+ expected_sha256="ab128162b0616090b47cf245afe0a23f3ef08936fdce19074f5ba0a4469281ac"
58
+ url="https://github.com/modelcontextprotocol/registry/releases/download/v${version}/${archive}"
59
+ curl --fail --location --proto '=https' --tlsv1.2 "$url" --output "$archive"
60
+ printf '%s %s\n' "$expected_sha256" "$archive" | sha256sum --check --strict
61
+ tar --extract --gzip --file "$archive" mcp-publisher
62
+ chmod 0755 mcp-publisher
63
+ ./mcp-publisher --version
64
+
65
+ - name: Authenticate to the MCP Registry (GitHub OIDC)
66
+ run: ./mcp-publisher login github-oidc
67
+
68
+ - name: Validate and publish server.json
69
+ run: |
70
+ ./mcp-publisher validate server.json
71
+ ./mcp-publisher publish
@@ -0,0 +1,195 @@
1
+ name: Release
2
+
3
+ # Tag-triggered publish to PyPI via Trusted Publishing (OIDC) - no API token stored
4
+ # in this repo. NOTE: the publish step only succeeds once a "trusted publisher" for
5
+ # this repo + workflow (release.yml, environment `pypi`) is configured on the PyPI
6
+ # project settings (https://docs.pypi.org/trusted-publishers/). OIDC works whether
7
+ # the GitHub repo is public or private; until that trusted publisher exists, pushing
8
+ # a v* tag builds correctly but the publish step fails at the PyPI end.
9
+ on:
10
+ push:
11
+ tags: ["v*"]
12
+
13
+ # Least privilege at the top level; the publish job opts into id-token below.
14
+ permissions:
15
+ contents: read
16
+
17
+ jobs:
18
+ verify:
19
+ name: Verify release candidate
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
23
+
24
+ - name: Install uv
25
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
26
+ with:
27
+ python-version: "3.11"
28
+ enable-cache: true
29
+
30
+ - name: Install development dependencies
31
+ run: uv sync --dev --locked
32
+
33
+ - name: Verify lint, types, tests, and distribution contracts
34
+ run: |
35
+ uv run ruff check .
36
+ uv run mypy src/volante
37
+ uv run pytest
38
+
39
+ build:
40
+ name: Build release artifacts
41
+ needs: verify
42
+ runs-on: ubuntu-latest
43
+ steps:
44
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0
45
+
46
+ - name: Install uv
47
+ uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
48
+ with:
49
+ python-version: "3.12"
50
+ enable-cache: true
51
+
52
+ - name: Build sdist + wheel
53
+ run: uv build
54
+
55
+ - name: Verify the git tag matches the built version
56
+ # A mislabeled tag would otherwise publish an immutable, wrongly-versioned
57
+ # artifact to PyPI. Parse the version straight from the built wheel filename
58
+ # (volante-<version>-py3-none-any.whl) and require tag `vX.Y.Z` == `X.Y.Z`.
59
+ run: |
60
+ tag="${GITHUB_REF_NAME#v}"
61
+ wheel="$(ls dist/*.whl | head -n1)"
62
+ built="$(basename "$wheel" | cut -d- -f2)"
63
+ echo "tag=$tag built=$built"
64
+ test "$tag" = "$built" || { echo "::error::tag v$tag != built version $built"; exit 1; }
65
+
66
+ - name: Check package metadata (twine)
67
+ run: uvx twine check dist/*
68
+
69
+ - name: Validate and build MCPB
70
+ run: |
71
+ version="${GITHUB_REF_NAME#v}"
72
+ npx --yes @anthropic-ai/mcpb@2.1.2 validate mcpb/manifest.json
73
+ npx --yes @anthropic-ai/mcpb@2.1.2 pack mcpb "volante-${version}.mcpb"
74
+ test -s "volante-${version}.mcpb"
75
+
76
+ - name: Upload Python distributions
77
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
78
+ with:
79
+ name: dist
80
+ path: dist/
81
+ if-no-files-found: error
82
+
83
+ - name: Upload MCPB
84
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
85
+ with:
86
+ name: mcpb
87
+ path: volante-*.mcpb
88
+ if-no-files-found: error
89
+
90
+ publish:
91
+ name: Publish to PyPI
92
+ needs: build
93
+ runs-on: ubuntu-latest
94
+ environment:
95
+ name: pypi
96
+ url: https://pypi.org/project/volante/
97
+ # Job-scoped: only this job gets the OIDC token used for Trusted Publishing.
98
+ permissions:
99
+ id-token: write
100
+ steps:
101
+ - name: Download build artifacts
102
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
103
+ with:
104
+ name: dist
105
+ path: dist/
106
+
107
+ - name: Publish to PyPI
108
+ uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
109
+ with:
110
+ # Idempotent: re-running a tag (e.g. after a history rewrite) must not fail
111
+ # on the immutable, already-published version.
112
+ skip-existing: true
113
+
114
+ - name: Verify PyPI actually serves THESE artifacts
115
+ # skip-existing makes a re-tagged, already-published version succeed silently.
116
+ # Combined with a forgotten version bump that is indistinguishable from a real
117
+ # release: the job is green, the GitHub Release is cut, and PyPI still serves the
118
+ # OLD code. Compare the digests instead of trusting the exit status.
119
+ env:
120
+ VERSION: ${{ github.ref_name }}
121
+ run: |
122
+ set -euo pipefail
123
+ version="${VERSION#v}"
124
+ curl -fsSL "https://pypi.org/pypi/volante/${version}/json" -o pypi.json
125
+ python - <<'PY'
126
+ import hashlib, json, os, pathlib, sys
127
+
128
+ version = os.environ["VERSION"].removeprefix("v")
129
+ published = {
130
+ entry["filename"]: entry["digests"]["sha256"]
131
+ for entry in json.load(open("pypi.json"))["urls"]
132
+ }
133
+ built = sorted(pathlib.Path("dist").iterdir())
134
+ if not built:
135
+ sys.exit("::error::no built artifacts to verify")
136
+ failures = []
137
+ for path in built:
138
+ local = hashlib.sha256(path.read_bytes()).hexdigest()
139
+ remote = published.get(path.name)
140
+ if remote is None:
141
+ failures.append(f"{path.name} is not published on PyPI for {version}")
142
+ elif remote != local:
143
+ failures.append(
144
+ f"{path.name}: PyPI serves {remote[:12]}… but this run built "
145
+ f"{local[:12]}… — the upload was skipped as already-existing, so "
146
+ f"version {version} was NOT actually released from this commit"
147
+ )
148
+ for failure in failures:
149
+ print(f"::error::{failure}")
150
+ sys.exit(1 if failures else 0)
151
+ PY
152
+
153
+ github-release:
154
+ name: Publish GitHub Release
155
+ needs: [build, publish]
156
+ runs-on: ubuntu-latest
157
+ permissions:
158
+ contents: write
159
+ steps:
160
+ - name: Download Python distributions
161
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
162
+ with:
163
+ name: dist
164
+ path: artifacts/dist
165
+
166
+ - name: Download MCPB
167
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
168
+ with:
169
+ name: mcpb
170
+ path: artifacts
171
+
172
+ - name: Create or refresh the release
173
+ env:
174
+ GH_TOKEN: ${{ github.token }}
175
+ run: |
176
+ version="${GITHUB_REF_NAME#v}"
177
+ assets=(artifacts/dist/* "artifacts/volante-${version}.mcpb")
178
+ if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
179
+ gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber
180
+ else
181
+ gh release create "$GITHUB_REF_NAME" "${assets[@]}" \
182
+ --verify-tag \
183
+ --title "Volante ${version}" \
184
+ --generate-notes
185
+ fi
186
+
187
+ publish-mcp:
188
+ name: Publish MCP Registry record
189
+ needs: [publish, github-release]
190
+ permissions:
191
+ contents: read
192
+ id-token: write
193
+ uses: ./.github/workflows/publish-mcp.yml
194
+ with:
195
+ ref: ${{ github.ref }}