-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add quota-aware Hermes specialist router #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b624174
26e2270
c6e4a2e
f8d3124
b72bf9b
f32707c
92d6490
72f70ea
3579ac1
8501e4b
0765c4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| # Quota-aware specialist model router | ||
|
|
||
| The `specialist-router` plugin keeps ordinary Telegram conversation on the configured Hermes coordinator and delegates coding work to two independently metered Codex models. | ||
|
|
||
| ## Policy | ||
|
|
||
| - Coordinator: `openai-api/gpt-5.6` for conversation, planning, summaries, status, and final reports. | ||
| - Spark: `gpt-5.3-codex-spark` for repository inspection, reproduction, focused tests, review, regression tests, and one bounded low-risk implementation attempt. | ||
| - Sol: `gpt-5.6-sol` immediately for high-risk or multi-file work, or after one failed/uncertain/incomplete Spark attempt. | ||
| - A successful sol implementation is independently reviewed by Spark. | ||
| - The router derives each pool's five-hour and weekly availability from Codex rollout telemetry and caches it for 120 seconds. At 20% weekly sol remaining, noncritical sol work stays on Spark; critical work may use the reserve. | ||
|
|
||
| The gateway hook leaves non-coding messages byte-for-byte unchanged. Coding messages receive an ephemeral route directive instructing the coordinator to invoke `route_specialist_task` once. The specialist runner feeds the complete prompt through Codex stdin (`-` + piped input) so multiline text survives intact, and it falls back to a coordinator/manual continuation when both specialist pools refuse to start. `/model-route-status` shows coordinator, routine/complex routes, active specialist sessions, task/repository, reason, five-hour and weekly quota state, the 20% Sol reserve, banked-reset availability/expiry, auto-review state, and the recommended burst state. Unsupported external values are reported as `unknown`; no banked reset is ever redeemed automatically. | ||
|
|
||
| Codex auto-review, if enabled by the CLI, is only a process signal. It is not code-quality approval. Every merge still requires a fresh exact-head GitHub Codex review (or an explicit human review decision), with findings resolved against the exact current SHA. | ||
|
|
||
| ## Configuration | ||
|
|
||
| Enable the bundled plugin and configure behavioral settings in `~/.hermes/config.yaml` (never `.env`): | ||
|
|
||
| ```yaml | ||
| plugins: | ||
| enabled: [specialist-router] | ||
| entries: | ||
| specialist-router: | ||
| coordinator_model: openai-api/gpt-5.6 | ||
| spark_model: gpt-5.3-codex-spark | ||
| sol_model: gpt-5.6-sol | ||
| reserve_percent: 20 | ||
| max_concurrent_editing: 2 | ||
| banked_reset_available: unknown | ||
| banked_reset_expires_at: null | ||
| auto_review_enabled: unknown | ||
| quota_cache_seconds: 120 | ||
| codex_binary: /home/ubuntu/.npm-global/bin/codex | ||
| ``` | ||
|
|
||
| Authentication remains in the existing global VM/Codex credential stores. The plugin never copies credentials into a project. | ||
|
|
||
| The installed CLI was verified with `codex exec --model` and exposes `codex exec resume <session-id> --model ...` for session reuse. Each specialist result records its returned thread ID; pass it back as `resume_session_id` so a follow-up resumes the compact specialist context instead of rereading a repository. | ||
|
|
||
| ## Rollback | ||
|
|
||
| Remove `specialist-router` from `plugins.enabled`, restore the prior `model` and `delegation` blocks in `~/.hermes/config.yaml`, and restart `hermes-gateway.service`. The only runtime state created by the plugin is `~/.hermes/specialist-router-state.json`, which may be left in place or removed after rollback. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| """Quota-aware specialist model router plugin.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import os | ||
| from pathlib import Path | ||
|
|
||
| from .router import Router, RouterConfig | ||
|
|
||
| _router: Router | None = None | ||
|
|
||
|
|
||
| def _config() -> RouterConfig: | ||
| try: | ||
| from hermes_cli.config import load_config | ||
| root = load_config() or {} | ||
| except Exception: | ||
| root = {} | ||
| entry = (((root.get("plugins") or {}).get("entries") or {}).get("specialist-router") or {}) | ||
| return RouterConfig.from_mapping(entry) | ||
|
|
||
|
|
||
| def register(ctx) -> None: | ||
| global _router | ||
| _router = Router(_config()) | ||
|
|
||
| def pre_gateway_dispatch(*, event, **_kwargs): | ||
| text = getattr(event, "text", "") or "" | ||
| decision = _router.classify(text) | ||
| if decision.route == "coordinator": | ||
| return {"action": "allow"} | ||
| directive = _router.route_directive(text, decision) | ||
| return {"action": "rewrite", "text": directive} | ||
|
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This hook rewrites every coding-looking gateway message whenever the plugin is loaded, but the plugin toolset can still be disabled per platform or omitted from a restricted session's Useful? React with 👍 / 👎.
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If an operator disables the Useful? React with 👍 / 👎.
Comment on lines
+33
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
If an operator keeps the plugin enabled but disables the Useful? React with 👍 / 👎. |
||
|
|
||
| def status(_raw_args: str = "") -> str: | ||
| return _router.format_status() | ||
|
|
||
| def route_tool(args: dict, **_kwargs) -> str: | ||
| result = _router.execute( | ||
| goal=str(args.get("goal") or ""), | ||
| repository=str(args.get("repository") or os.getcwd()), | ||
| risk=str(args.get("risk") or "auto"), | ||
| simulate_spark_failure=bool(args.get("simulate_spark_failure", False)), | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because this handler accepts Useful? React with 👍 / 👎. |
||
| resume_session_id=str(args.get("resume_session_id") or "") or None, | ||
| ) | ||
| return json.dumps(result, ensure_ascii=False) | ||
|
|
||
| ctx.register_hook("pre_gateway_dispatch", pre_gateway_dispatch) | ||
| ctx.register_command( | ||
| "model-route-status", | ||
| handler=status, | ||
| description="Show specialist routing, quota pools, and sol reserve state.", | ||
| ) | ||
| ctx.register_tool( | ||
| name="route_specialist_task", | ||
| toolset="specialist-router", | ||
| description="Route one coding goal to Spark or GPT-5.6-sol and return its verified result.", | ||
| emoji="⇄", | ||
| schema={ | ||
| "name": "route_specialist_task", | ||
| "description": "Execute a coding task through the quota-aware Codex specialist router.", | ||
| "parameters": { | ||
| "type": "object", | ||
| "properties": { | ||
| "goal": {"type": "string"}, | ||
| "repository": {"type": "string"}, | ||
| "risk": {"type": "string", "enum": ["auto", "low", "high", "critical"]}, | ||
| "simulate_spark_failure": {"type": "boolean"}, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Because this property is in the model-visible tool schema and Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Exposing Useful? React with 👍 / 👎. |
||
| "resume_session_id": {"type": "string", "description": "Optional Codex thread ID to resume instead of rereading repository context."}, | ||
| }, | ||
| "required": ["goal", "repository"], | ||
| }, | ||
| }, | ||
| handler=route_tool, | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| name: specialist-router | ||
| version: 1.0.0 | ||
| description: Quota-aware Codex specialist routing for coding tasks received by Hermes gateways. | ||
| author: DJ Papzin | ||
| hooks: | ||
| - pre_gateway_dispatch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This hook classifies the raw gateway text before command dispatch and does not exempt
event.is_command(). Built-in, quick, plugin, or skill commands whose name or arguments contain words likefix,test,lint, orrepowill be rewritten into a specialist-route prompt before their handler sees them, so commands such as/queue fix this bugor a coding skill command no longer preserve their intended slash-command semantics.Useful? React with 👍 / 👎.