solar-router
Shared router that runs AI providers (Codex, Claude, Agy/Antigravity, Agent, Ollama) with Solar repo context. Single source of truth for provider selection, fallback, and async routing policy. Use whe
SKILL.mdcore/skills/solar-router/SKILL.md
GitHubSolar Router
Purpose
Single source of truth for all AI execution in Solar:
- Provider selection and fallback live only here.
- Async routing policy (
direct_replyvsasync_draft_created) lives only here. - Used by solar-gateway (WebSocket/bridge) and solar-async-tasks (task execution).
Operational boundary
Use this skill as infrastructure for Solar runtimes and controlled diagnostics. Do not use solar-router or provider CLIs as the normal path for work that is deferred, multiprovider, browser/MCP-dependent, network/auth/keychain-dependent, long-running, or likely to block the conversation.
For that work, create or propose a task through solar-async-tasks. When solar-system supervises async-tasks, approve/queue the task and let the system runtime execute it.
Scope
- Accept JSON payload (router contract v3) on stdin; output structured JSON on stdout.
- Run the selected provider with
cwd=SOLAR_WORKSPACEso all providers seesun/,planets/, and workspaceAGENTS.md. - Resolve
SOLAR_ROUTER_SYSTEM_PROMPT_FILEandSOLAR_ROUTER_RUNTIME_DIRagainstSOLAR_WORKSPACEwhen relative. - Codex default command includes
-C <repo-root>and--add-dir ~/.codex. - Persist conversation turns in runtime dir (JSONL) and inject continuity into each prompt: rolling
*-summary.txtfrom<solar_summary>plus recent turns (SOLAR_ROUTER_CONTEXT_TURNS). - Inject the cross-channel intention from the continuity row in
state.sqlite. Seereferences/continuity.md. - Own the A3 mandate CLI (
scripts/delegation_ctl.py). The YAML insun/delegations/is opened bysolar-state. Any caller gates mutating routines throughcheckand fails closed. Seereferences/a3-mandates.md. Unrelated to JIT agent/skill delegation. - Answer "where are we" on demand with
scripts/work_status.sh(intention, machine queue, today's blockers, mandates). Read-only, no cadence: periodic briefings are recurring async tasks. Behaviour layer inreferences/signal-orchestration.md. - Implement
DecisionEngine: decidedecision.kindbased onmode,channel, and AI semantic output. On telegram/n8n/app,async_draft_createdqueues work +notify_when: completedand returns a short ACK. - Resolve JIT context from
metadata: lookup agent/skills in planet → fallback to core → generate role inline if not found. - Append
start/endaudit rows throughsolar-statefor each execution (including failed early-exit paths).
Restricted reviews
Internal ID/token-bound requests use text_only_review_v1 for packets or
repository_readonly_v1 for committed repository exploration before interactive
context loading. execution_profiles.py composes and verifies immutable prompts
and builds the sole restricted command/environment for run and stream. The packet adapter remains Claude-only; repository readers have guarded Claude, Codex, Cursor and Agy adapters.
Provider-specific host guards refuse unsupported configurations.
No profile is enabled in production. See the profile contract
for build_review_envelope_v1, manifest fields, launch preflight and limitations.
Internal architecture
scripts/
run_router.py — thin entrypoint: stdin → route() → stdout + exit
router.py — all provider-agnostic logic (parse, validate, JIT, prompt, decision engine)
providers/
__init__.py — PROVIDERS dict, exports all adapters
base.py — BaseProvider: resolve_binary, get_cmd, prepare_env, clean_output, run
claude.py — static default_cmd
codex.py — build_default_cmd() with SOLAR_WORKSPACE + CODEX_STATE_DIR
agy.py — Antigravity CLI (`agy -p`)
agent.py — build_default_cmd() with SOLAR_WORKSPACE
Automated tests live under `core/tests/skills/solar-router/` (framework-wide layout; see `core/AGENTS.md`).
Layer contract:
router.pydecides (context, prompt, policy, response shape).providers/executes (subprocess, env, normalization). No routing logic.run_router.pydoes I/O only. No logic.
Required MCP
None
Setup
# Configure router environment variables (SOLAR_ROUTER_*, timeouts, etc.)
bash core/skills/solar-router/scripts/onboard_router_env.sh
Key environment variables:
SOLAR_ROUTER_PROVIDER_PRIORITY— Comma-separated provider list (e.g.,codex,claude,agy,agent,ollama)SOLAR_ROUTER_RUNTIME_DIR— Where conversation history is stored (default:<runtime root>/router)SOLAR_ROUTER_SYSTEM_PROMPT_FILE— System prompt file path (default:core/skills/solar-router/assets/system_prompt.md)SOLAR_ROUTER_CONTEXT_TURNS— Number of conversation turns to include (default:12)SOLAR_ROUTER_TIMEOUT_SEC— End-to-end router timeout, including provider execution (default:300)SOLAR_ROUTER_REVIEW_EVIDENCE_DIR/SOLAR_ROUTER_REVIEW_EVIDENCE_KEEP— Opt-in local copy of Codex repository-review output (default: off; set in the environment of the process that runs the review, not the launching session; the output contains model text and repository reads, so it is sensitive; seecore/docs/repository-readonly-review.md)
Optional command overrides:
SOLAR_ROUTER_CODEX_CMDSOLAR_ROUTER_CLAUDE_CMDSOLAR_ROUTER_AGY_CMDSOLAR_ROUTER_OLLAMA_CMD
Ollama setup:
provider=ollamaalways targets the local model namedsolar- Build or refresh it with
bash core/skills/solar-router/scripts/setup_ollama.sh
Validation commands
# Validate skill structure
python3 core/skills/solar-skill-creator/scripts/package_skill.py core/skills/solar-router /tmp
# List configured providers (reads SOLAR_ROUTER_PROVIDER_PRIORITY)
bash core/skills/solar-router/scripts/list_providers.sh
bash core/skills/solar-router/scripts/list_providers.sh --exclude claude
bash core/skills/solar-router/scripts/list_providers.sh --exclude claude --format csv
# Diagnose router / preflight providers (native helper in this skill)
bash core/skills/solar-router/scripts/diagnose_router.sh --dry-run
bash core/skills/solar-router/scripts/diagnose_router.sh
# Full error output when a provider fails (e.g. 401, binary not found)
bash core/skills/solar-router/scripts/diagnose_router.sh --verbose
# Unit tests: router logic + provider adapters (centralized under core/tests; pytest runs unittest-style tests; no real AI calls)
uv run --project core/tests pytest core/tests/skills/solar-router -q
# Without uv: PYTHONPATH=core/skills/solar-router/scripts python3 -m unittest discover -s core/tests/skills/solar-router -p "test_*.py" -v
# Smoke tests: validate router contract v3, bridge delegation, execute_active.py JSON parsing
bash core/skills/solar-router/scripts/check_router.sh
# Live status: provider health, in-flight processes, last executions
bash core/skills/solar-router/scripts/status_router.sh
bash core/skills/solar-router/scripts/status_router.sh --last 20
# Close historical orphan audit records (append reconciled end events)
bash core/skills/solar-router/scripts/reconcile_router_audit.sh --dry-run
bash core/skills/solar-router/scripts/reconcile_router_audit.sh
Router contract v3
Input (stdin JSON)
{
"request_id": "string",
"session_id": "string",
"user_id": "string",
"text": "string",
"channel": "telegram|n8n|async-task|other",
"mode": "auto|direct_only|async_only",
"provider": "codex|claude|agy|agent|ollama|null",
"metadata": {
"agent": "agent-name|null",
"skills": ["planet:skill-name", "core-skill-name"],
"planet": "planet-name|null"
}
}
provider: optional. If set, strict mode — no fallback. If fails →error_code: provider_locked_failed.mode: defaults toauto.direct_onlyalways returnsdirect_reply.async_onlyrequiresasync-tasksfeature enabled.channel: used byDecisionEnginefor semantic routing inmode=auto.metadata.agent: existing agent name from planet'sagents/, ornullfor JIT role generation.metadata.skills: compatibility metadata only;resolve_jit_context()does not resolve or inject these descriptions. The provider harness discovers instruction skills.metadata.planet: planet that owns the task domain. Used for agent lookup.
Secure Invocation Protocol (Required)
To prevent JSON parsing errors (invalid control characters, unescaped newlines), always use one of these two methods when calling the router from an AI agent or shell:
Method A: Temporary JSON File (Recommended for Agents)
- Use
write_fileto create a temporary JSON file (e.g.,<runtime root>/router/request_<id>.json). - Ensure the
textfield contains explicit\nfor newlines. - Execute the router piping the file:
python3 core/skills/solar-router/scripts/run_router.py < <runtime root>/router/request_<id>.json.
Method B: Heredoc with Single Quotes (Shell)
Use a heredoc with 'EOF' (single quotes) to prevent the shell from interpreting backslashes or special characters:
cat << 'EOF' | python3 core/skills/solar-router/scripts/run_router.py
{
"request_id": "my-id",
"text": "Line 1\nLine 2",
"channel": "other",
"mode": "direct_only"
}
EOF
Critical Rules:
- Prefer escaped newlines (
\n) inside JSON strings for portability. - Escape double quotes inside the
textstring as\". - Validate JSON before sending if using a custom script.
Output (stdout JSON)
{
"status": "success|failed",
"request_id": "string",
"provider_used": "codex|claude|agy|agent|ollama",
"reply_text": "string",
"decision": {
"kind": "direct_reply|async_draft_proposal|async_draft_created|async_activation_needed",
"task_id": "string|null",
"priority_suggested": "high|normal|low|null"
},
"error_code": "string|null",
"error": "string|null"
}
DecisionEngine rules
mode=direct_only→decision.kind=direct_replyalways.mode=async_only+async-tasksenabled →decision.kind=async_draft_created.mode=async_only+async-tasksdisabled →status=failed+ explicit error.mode=auto+channel=async-task→decision.kind=direct_reply(already in queue).mode=auto+channel=telegram|n8n|other→ AI decides semantically via structured JSON output.
Consumers
- solar-gateway:
run_websocket_bridge.pyand HTTP webhook bridge call the router with full v3 contract. - solar-async-tasks:
execute_active.py(viaexecute_active.sh) calls the router withchannel=async-task,mode=direct_only.
Runtime files
<runtime root>/router/conversations/<user_id>.jsonl— conversation history per user (for context continuity).- Router audit rows in
state.sqlite— onestart/endpair per execution. Fields:router_id(internal UUID),request_id(caller ref),user_id,metadata,provider,status,jit_generated,duration_ms.
References
references/routing-policy.md— provider priority, env keys, repo-context policy, v3 contract rules.
Managed execution
managed_process.py is an internal helper used by app and async executors to bound and cancel router process groups; it is not a separate provider entrypoint.
Repository review profile
repository_readonly_v1 explores a private copy of an approved Git commit through
Router with an exact provider/model. Native task.create/task.update prepare
review inputs or a bounded review_batch; approval and pool admission remain
separate. Claude, Codex, Cursor (agent) and Antigravity (agy) have guarded constructors;
unknown providers refuse.
The packet profile is unchanged and production profiles remain disabled. See
repository review for scope, host
policy checks, CLI restrictions, limitations and the opt-in inference-free probe.