arkaos 4.1.1 → 4.3.0
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.
- package/README.md +1 -1
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -1
- package/arka/skills/flow/SKILL.md +15 -0
- package/arka/skills/fusion/SKILL.md +91 -0
- package/config/constitution.yaml +15 -7
- package/config/hooks/session-start.sh +22 -0
- package/config/models.yaml +70 -0
- package/core/fusion/__init__.py +5 -0
- package/core/fusion/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/fusion/__pycache__/cli.cpython-313.pyc +0 -0
- package/core/fusion/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/fusion/cli.py +37 -0
- package/core/fusion/engine.py +153 -0
- package/core/governance/__pycache__/redo_counter.cpython-313.pyc +0 -0
- package/core/governance/redo_counter.py +81 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
- package/core/hooks/pre_tool_use.py +55 -3
- package/core/obsidian/__pycache__/relator.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/model_router.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/model_router_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/ollama_discovery.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/openrouter_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/openrouter_provider.cpython-314.pyc +0 -0
- package/core/runtime/llm_provider.py +10 -1
- package/core/runtime/model_router.py +204 -0
- package/core/runtime/model_router_cli.py +151 -0
- package/core/runtime/ollama_discovery.py +96 -0
- package/core/runtime/openrouter_provider.py +172 -0
- package/core/sync/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/sync/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/sync/__pycache__/manifest.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/frontend_gate.cpython-313.pyc +0 -0
- package/core/workflow/frontend_gate.py +162 -0
- package/dashboard/app/layouts/default.vue +7 -0
- package/dashboard/app/pages/models.vue +404 -0
- package/installer/autostart.js +16 -2
- package/installer/cli.js +23 -0
- package/installer/keys.js +1 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/dashboard-api.py +79 -0
- package/scripts/start-dashboard.sh +25 -1
package/README.md
CHANGED
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.3.0
|
package/arka/SKILL.md
CHANGED
|
@@ -24,7 +24,7 @@ does not replace the vault.
|
|
|
24
24
|
# ArkaOS — Main Orchestrator
|
|
25
25
|
|
|
26
26
|
> **The Operating System for AI Agent Teams**
|
|
27
|
-
> 82 agents. 17 departments.
|
|
27
|
+
> 82 agents. 17 departments. 268 skills. Multi-runtime. Dashboard. Knowledge RAG.
|
|
28
28
|
|
|
29
29
|
## ⛔ Evidence flow — 4 gates (NON-NEGOTIABLE)
|
|
30
30
|
|
|
@@ -87,6 +87,21 @@ resumes at the right gate.
|
|
|
87
87
|
coverage read from the report file, security grep, spell-check for
|
|
88
88
|
copy. Reviewers (Quality Gate personas) interpret tool output; they do
|
|
89
89
|
not replace it. APPROVED/REJECTED derives from evidence.
|
|
90
|
+
- **Excellence check (`excellence-mandate`, mandatory):** before closing,
|
|
91
|
+
answer three questions with evidence, not narration:
|
|
92
|
+
1. What is **unfinished** in this delivery (trimmed scope, TODO left
|
|
93
|
+
behind, parity skipped)?
|
|
94
|
+
2. What is **default** (template look, boilerplate copy, unstyled
|
|
95
|
+
component, generic strategy)?
|
|
96
|
+
3. What would a **top-tier lead reject** here? Name the benchmark
|
|
97
|
+
(`reference_companies.application` — e.g. Linear/Stripe for
|
|
98
|
+
frontend) and judge against it.
|
|
99
|
+
A non-empty answer loops the work back into Gate 3 or is escalated to
|
|
100
|
+
the operator as an explicit open-items decision. Shipping with a
|
|
101
|
+
non-empty list and no operator decision is a constitution breach.
|
|
102
|
+
Time and token cost are not acceptable answers to any of the three.
|
|
103
|
+
- Quality Gate REJECTED loops back at most twice; a third REJECTED
|
|
104
|
+
escalates to the operator with the full verdict.
|
|
90
105
|
- Close with an honest summary: what changed, where, how it was
|
|
91
106
|
verified (real commands + results), what remains open.
|
|
92
107
|
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: arka-fusion
|
|
3
|
+
description: >
|
|
4
|
+
Model Fabric advisor and fusion configurator. Discovers what the machine
|
|
5
|
+
can run (Ollama local models, OpenRouter/Anthropic keys), reads the
|
|
6
|
+
current role routing, interviews the user about goals and constraints,
|
|
7
|
+
and recommends — with rationale — which model should run each role and
|
|
8
|
+
which panel+judge combination to use for fusion. Applies the approved
|
|
9
|
+
configuration via `npx arkaos models`. Trigger: "/arka-fusion", "fusion",
|
|
10
|
+
"que modelos usar", "configurar modelos", "model routing".
|
|
11
|
+
allowed-tools: [Read, Bash, AskUserQuestion]
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# /arka-fusion — Model Fabric Advisor
|
|
15
|
+
|
|
16
|
+
You are the Model Fabric advisor. Your job: leave the user with a role →
|
|
17
|
+
model mapping and a fusion panel that fit THEIR machine, keys, and goals —
|
|
18
|
+
never a generic default. Constitution `model-routing` sets the posture:
|
|
19
|
+
quality-critical roles get the best model available; only genuinely
|
|
20
|
+
mechanical work economises.
|
|
21
|
+
|
|
22
|
+
## Phase 1 — Discover (never skip, never assume)
|
|
23
|
+
|
|
24
|
+
Run and read all three:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
python -m core.runtime.model_router_cli discover # Ollama + local models
|
|
28
|
+
python -m core.runtime.model_router_cli --json # current routing
|
|
29
|
+
python -m core.runtime.model_router_cli usage --period week --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Also check key presence (never print values): `OPENROUTER_API_KEY` /
|
|
33
|
+
`ANTHROPIC_API_KEY` in env or `~/.arkaos/keys.json`.
|
|
34
|
+
|
|
35
|
+
Interpret the discovery honestly:
|
|
36
|
+
- Ollama **not installed** → local lane unavailable; say what installing
|
|
37
|
+
it would unlock (free mechanical/panel calls, privacy).
|
|
38
|
+
- Ollama installed but **not running** → tell the user to start it; count
|
|
39
|
+
its models as one `ollama serve` away.
|
|
40
|
+
- Ollama running → classify each local model by size/family: coder models
|
|
41
|
+
(qwen-coder, deepseek-coder) suit `mechanical` + code review panels;
|
|
42
|
+
large general models (30B+, kimi cloud) qualify for panel seats;
|
|
43
|
+
sub-8B models are mechanical-only. `:cloud` models are Ollama-proxied
|
|
44
|
+
remote models — panel-grade, not privacy-local.
|
|
45
|
+
|
|
46
|
+
## Phase 2 — Interview (one AskUserQuestion, then dialogue)
|
|
47
|
+
|
|
48
|
+
Ask about, at most 4 questions, adapting to what discovery found:
|
|
49
|
+
1. **Prioridade** — máxima qualidade custe o que custar / equilíbrio /
|
|
50
|
+
privacidade local primeiro?
|
|
51
|
+
2. **Fusion** — ativar painel multi-modelo para trabalho crítico
|
|
52
|
+
(2-5× chamadas por resposta, qualidade validada acima de qualquer
|
|
53
|
+
modelo solo) ou manter single-model por agora?
|
|
54
|
+
3. **OpenRouter** — se não há chave: quer criar uma? (1 chave → Kimi,
|
|
55
|
+
DeepSeek, GPT, Gemini como participantes de painel.)
|
|
56
|
+
4. Qualquer ambiguidade real que a discovery levantou.
|
|
57
|
+
|
|
58
|
+
## Phase 3 — Recommend (framework-backed, named tradeoffs)
|
|
59
|
+
|
|
60
|
+
Produce a table: role → provider/model + ONE-line rationale each. Rules:
|
|
61
|
+
- `design/review/architecture/strategy/quality_gate` → best frontier
|
|
62
|
+
model available. NEVER a local small model, even if the user leans
|
|
63
|
+
cost-sensitive — offer fusion-with-budget-panel instead (OpenRouter
|
|
64
|
+
evidence: budget panel + frontier judge beats frontier solo at ~50%
|
|
65
|
+
cost).
|
|
66
|
+
- `mechanical` → cheapest competent lane: local coder model if Ollama
|
|
67
|
+
runs one, else haiku-class.
|
|
68
|
+
- Fusion panel: 2-3 DIVERSE models (different families — e.g. one
|
|
69
|
+
Anthropic, one Kimi/DeepSeek, one local large) + frontier judge.
|
|
70
|
+
Homogeneous panels waste the fan-out.
|
|
71
|
+
- Cite `usage` data when it argues for a change ("80% of your tokens are
|
|
72
|
+
mechanical → moving them local saves X").
|
|
73
|
+
|
|
74
|
+
## Phase 4 — Apply (only after explicit approval)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx arkaos models set <role> <provider>/<model> --effort <effort>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
For fusion panel/judge and aliases, edit `~/.arkaos/models.yaml` directly
|
|
81
|
+
(keep comments). Show the final `npx arkaos models` table as proof and
|
|
82
|
+
remind: the dashboard Models page shows the same state + live usage.
|
|
83
|
+
|
|
84
|
+
## Hard rules
|
|
85
|
+
|
|
86
|
+
- Discovery before advice — recommending a model the machine cannot run
|
|
87
|
+
is a constitution breach (`context-verification`).
|
|
88
|
+
- Never print key values. Presence only.
|
|
89
|
+
- Quality roles never downgrade below frontier (`model-routing`,
|
|
90
|
+
`excellence-mandate`). Push back if asked; offer budget fusion instead.
|
|
91
|
+
- Every recommendation carries a rationale the user can disagree with.
|
package/config/constitution.yaml
CHANGED
|
@@ -117,14 +117,18 @@ enforcement_levels:
|
|
|
117
117
|
rule: "When a squad lead dispatches a specialist via the Agent tool, the lead MUST emit `[arka:dispatch] <from> -> <to>` on a line of its own immediately before the dispatch call. The marker identifies the specialist to the PreToolUse specialist-enforcer so writes from the specialist pass `owner-match` instead of falling through as `no-routing-tag` or blocking the lead. The format is identical to `[arka:routing]` but uses the verb `dispatch` and points from the calling persona to the receiving specialist (e.g., `[arka:dispatch] paulo -> frontend-dev`)."
|
|
118
118
|
enforcement: "PreToolUse hook (config/hooks/pre-tool-use.sh) reads the dispatch marker via core.workflow.specialist_enforcer._resolve_persona. Dispatch tag overrides routing tag because it is more specific. Without it, lead writes to specialist-owned files are blocked when hooks.specialistEnforcement=true. See ADR docs/adr/2026-05-28-specialist-dispatch-subagent-blindspot.md for the architectural constraint this rule mitigates."
|
|
119
119
|
|
|
120
|
+
# ─── Rule added in Excellence Reform (2026-07-05, operator mandate) ──
|
|
121
|
+
- id: excellence-mandate
|
|
122
|
+
rule: "Every deliverable targets excellence, not acceptance. Supervisors (leads, C-Suite, Quality Gate) and specialists are REQUIRED to be maximally critical and meticulous: no default-looking output, no unfinished edges, no lazy implementations, no delivery-for-the-sake-of-delivering. Latency and token cost are NEVER arguments against quality work — the CostGovernor hard budget is the only ceiling. Before any gate closes, the closing agent answers with evidence: 'what is unfinished, what is default, what would a top-tier lead (see reference_companies.application) reject here?' — a non-empty answer loops the work back or escalates to the operator; it is never silently shipped. UI work MUST load the frontend design skills (frontend-design, ui-ux-pro-max, project design system) at maximum effort and pass visual review against a named benchmark before reaching the Quality Gate."
|
|
123
|
+
enforcement: "G4 excellence check in arka/skills/flow/SKILL.md; frontend gate core/workflow/frontend_gate.py (flag hooks.frontendGate); QG reviewers must cite the named benchmark and enumerate concrete rejections; REJECTED redo loop capped at 2 cycles then operator escalation. Operator mandate 2026-07-05: 'não interessa se demora — o goal é excelência'."
|
|
124
|
+
|
|
120
125
|
quality_gate:
|
|
121
126
|
description: "Mandatory pre-delivery review. Nothing ships without APPROVED verdict."
|
|
122
127
|
trigger: "After the last execution phase, before delivery to user"
|
|
123
128
|
frequency: "Once per workflow execution, not per phase"
|
|
124
129
|
model_policy:
|
|
125
|
-
default:
|
|
126
|
-
|
|
127
|
-
note: "Marta's veto is model-independent: the verdict derives from the evidence report (core.governance.evidence_checks), not from model size."
|
|
130
|
+
default: best-available
|
|
131
|
+
note: "Quality Gate reviews run the best model available (frontier tier) at maximum effort by default — the Excellence Reform (2026-07-05) inverted the former sonnet-default cost posture; review quality is never the place to save tokens. User overrides per role live in ~/.arkaos/models.yaml (Model Fabric). Marta's veto is model-independent: the verdict derives from the evidence report (core.governance.evidence_checks), not from model size."
|
|
128
132
|
agents:
|
|
129
133
|
orchestrator:
|
|
130
134
|
id: cqo-marta
|
|
@@ -140,9 +144,10 @@ enforcement_levels:
|
|
|
140
144
|
process:
|
|
141
145
|
- "Evidence engine runs first: python -m core.governance.evidence_checks — the verdict derives from its report"
|
|
142
146
|
- "Marta receives all output from execution phase plus the evidence report"
|
|
143
|
-
- "Marta dispatches Eduardo (text) and Francisca (technical) in parallel
|
|
144
|
-
- "Each reviewer returns a structured QGVerdict (APPROVED or REJECTED) with specific issues; evidence overall=fail forces REJECTED"
|
|
147
|
+
- "Marta dispatches Eduardo (text) and Francisca (technical) in parallel, each in an INDEPENDENT subagent context (clean context, no stake in the work being approved)"
|
|
148
|
+
- "Each reviewer returns a structured QGVerdict (APPROVED or REJECTED) with specific issues; the verdict MUST name the benchmark used (reference_companies.application) and enumerate the concrete rejections a top-tier lead would raise; evidence overall=fail forces REJECTED"
|
|
145
149
|
- "If ANY reviewer rejects: work loops back to execution with issue list"
|
|
150
|
+
- "Redo loop is capped at 2 cycles: a third REJECTED escalates to the operator with the full verdict instead of another silent retry (excellence-mandate)"
|
|
146
151
|
- "If ALL approve: Marta issues final APPROVED verdict"
|
|
147
152
|
- "Nothing reaches the user without APPROVED from all three"
|
|
148
153
|
|
|
@@ -182,11 +187,11 @@ enforcement_levels:
|
|
|
182
187
|
enforcement: "persistence.py exports on status change; audit detects missing exports"
|
|
183
188
|
|
|
184
189
|
- id: model-routing
|
|
185
|
-
rule: "
|
|
190
|
+
rule: "Quality-first model routing: design, review, architecture, strategy, and Quality Gate phases run the BEST model available for the task (frontier tier) at maximum effort BY DEFAULT. Cheaper/smaller models (haiku, local) are reserved for genuinely mechanical work: commit messages, keyword extraction, data fetching, formatting. Cost optimisation NEVER downgrades a quality-critical phase — the CostGovernor hard budget is the only ceiling. User-owned overrides live in ~/.arkaos/models.yaml (Model Fabric): the user decides which provider/model serves each role; this rule sets the default posture, not a fixed vendor table. Replaced the cost-optimised per-tier table in the Excellence Reform (2026-07-05)."
|
|
186
191
|
enforcement: "warning"
|
|
187
192
|
|
|
188
193
|
- id: subagent-discipline
|
|
189
|
-
rule: "Dispatch subagents only when task requires >3 Reads or >5 Greps or isolated context. Never parallel subagents sharing state. Prefer main thread for trivial tasks."
|
|
194
|
+
rule: "Dispatch subagents only when task requires >3 Reads or >5 Greps or isolated context. Never parallel subagents sharing state. Prefer main thread for trivial tasks. EXEMPT: quality dispatches (Quality Gate reviewers, adversarial verification, design review) are never suppressed by this rule — independent review context is a correctness requirement under excellence-mandate, not an optimisation choice."
|
|
190
195
|
enforcement: "warning"
|
|
191
196
|
|
|
192
197
|
# ─── Rule added in PR3 Squad Intelligence Upgrade (2026-05-28) ───────
|
|
@@ -436,3 +441,6 @@ amendments:
|
|
|
436
441
|
- version: "2.32.0"
|
|
437
442
|
date: "2026-05-13"
|
|
438
443
|
changes: "PR10 Conclave Phase 5 — added 7 NON-NEGOTIABLE rules (quality-over-speed, always-research, project-design-system-prerequisite, definition-of-done-per-domain, arkaos-not-yes-man, inter-agent-checkpoints, hybrid-learning) and 3 new top-level sections (definition_of_done per domain, reference_companies, tone_guide)."
|
|
444
|
+
- version: "4.2.0"
|
|
445
|
+
date: "2026-07-05"
|
|
446
|
+
changes: "Excellence Reform (operator mandate) — added excellence-mandate NON-NEGOTIABLE rule; inverted model-routing from cost-optimised tiers to quality-first (best model for design/review/architecture/strategy, user overrides in ~/.arkaos/models.yaml); QG model_policy sonnet-default → best-available; QG reviewers independent + benchmark-cited verdicts + redo loop capped at 2; subagent-discipline exempts quality dispatches."
|
|
@@ -120,6 +120,28 @@ if [ -n "$REPO" ] && command -v python3 &>/dev/null; then
|
|
|
120
120
|
fi
|
|
121
121
|
fi
|
|
122
122
|
|
|
123
|
+
# ─── Dashboard ensure (v4.1.2) ──────────────────────────────────────────
|
|
124
|
+
# Safety net for the login autostart (`npx arkaos autostart enable`): if the
|
|
125
|
+
# dashboard is healthy this is two curl health checks (~50ms); if not, it
|
|
126
|
+
# starts API+UI. Background + disown so it never touches the 5s budget.
|
|
127
|
+
# Toggle off: ~/.arkaos/config.json -> {"dashboard": {"ensure_on_session": false}}
|
|
128
|
+
if [ -n "$REPO" ] && [ -f "$REPO/scripts/start-dashboard.sh" ]; then
|
|
129
|
+
_DASH_ENSURE=$(python3 -c "import json; print(json.load(open('$HOME/.arkaos/config.json')).get('dashboard',{}).get('ensure_on_session', True))" 2>/dev/null || echo "True")
|
|
130
|
+
if [ "$_DASH_ENSURE" = "True" ] || [ "$_DASH_ENSURE" = "true" ]; then
|
|
131
|
+
mkdir -p "$HOME/.arkaos/logs"
|
|
132
|
+
(
|
|
133
|
+
# GNU `timeout` is absent on stock macOS — degrade to an unbounded
|
|
134
|
+
# run; the launcher itself is bounded (health wait + start).
|
|
135
|
+
if command -v timeout >/dev/null 2>&1; then
|
|
136
|
+
ARKAOS_NO_BROWSER=1 timeout 90s bash "$REPO/scripts/start-dashboard.sh" ensure >> "$HOME/.arkaos/logs/dashboard-ensure.log" 2>&1
|
|
137
|
+
else
|
|
138
|
+
ARKAOS_NO_BROWSER=1 bash "$REPO/scripts/start-dashboard.sh" ensure >> "$HOME/.arkaos/logs/dashboard-ensure.log" 2>&1
|
|
139
|
+
fi
|
|
140
|
+
) &
|
|
141
|
+
disown 2>/dev/null || true
|
|
142
|
+
fi
|
|
143
|
+
fi
|
|
144
|
+
|
|
123
145
|
# --- Session Memory Resume Context ---
|
|
124
146
|
if command -v python3 &>/dev/null && [ -n "$REPO" ]; then
|
|
125
147
|
_SESSION_CTX=$(cd "$REPO" && python3 -c "
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2
|
+
# ArkaOS Model Fabric — who runs what.
|
|
3
|
+
#
|
|
4
|
+
# Your copy lives at ~/.arkaos/models.yaml (created by `npx arkaos models
|
|
5
|
+
# init`; this file is the packaged default). Edit it directly or use:
|
|
6
|
+
#
|
|
7
|
+
# npx arkaos models show every role and what it resolves to
|
|
8
|
+
# npx arkaos models set review anthropic/best
|
|
9
|
+
# npx arkaos models set mechanical ollama/qwen3:8b
|
|
10
|
+
#
|
|
11
|
+
# The interactive runtime (Claude Code, Codex, Gemini, Cursor) keeps its
|
|
12
|
+
# own main-loop model — roles govern everything ArkaOS itself dispatches:
|
|
13
|
+
# subagents, Quality Gate, Forge, fusion, cognition, daemons.
|
|
14
|
+
#
|
|
15
|
+
# Constitution `model-routing` (Excellence Reform 2026-07-05): quality-
|
|
16
|
+
# critical roles run the BEST model available by default; only genuinely
|
|
17
|
+
# mechanical work economises. Change the mapping, not the posture.
|
|
18
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
19
|
+
version: 1
|
|
20
|
+
|
|
21
|
+
# ─── Providers ──────────────────────────────────────────────────────────
|
|
22
|
+
# type must match a provider in core/runtime/llm_provider.py.
|
|
23
|
+
providers:
|
|
24
|
+
runtime:
|
|
25
|
+
type: subagent # shell out to the active runtime CLI
|
|
26
|
+
anthropic:
|
|
27
|
+
type: anthropic-direct # SDK; needs ANTHROPIC_API_KEY
|
|
28
|
+
ollama:
|
|
29
|
+
type: ollama # local models, http://localhost:11434
|
|
30
|
+
# openrouter: # one key -> hundreds of models (Model Fabric PR-B)
|
|
31
|
+
# type: openrouter
|
|
32
|
+
|
|
33
|
+
# ─── Aliases ────────────────────────────────────────────────────────────
|
|
34
|
+
# `best` / `default` / `fast` resolve per provider, so roles stay readable.
|
|
35
|
+
aliases:
|
|
36
|
+
runtime:
|
|
37
|
+
best: opus
|
|
38
|
+
default: sonnet
|
|
39
|
+
fast: haiku
|
|
40
|
+
anthropic:
|
|
41
|
+
best: claude-opus-4-8
|
|
42
|
+
default: claude-sonnet-5
|
|
43
|
+
fast: claude-haiku-4-5-20251001
|
|
44
|
+
ollama:
|
|
45
|
+
best: "" # set your strongest local model, e.g. kimi-k2.6
|
|
46
|
+
default: ""
|
|
47
|
+
fast: ""
|
|
48
|
+
|
|
49
|
+
# ─── Roles ──────────────────────────────────────────────────────────────
|
|
50
|
+
# provider/model per kind of work. `model` is an alias above or a literal
|
|
51
|
+
# model id. `effort`: low | medium | high | max.
|
|
52
|
+
roles:
|
|
53
|
+
design: {provider: runtime, model: best, effort: max}
|
|
54
|
+
review: {provider: runtime, model: best, effort: max}
|
|
55
|
+
architecture: {provider: runtime, model: best, effort: max}
|
|
56
|
+
strategy: {provider: runtime, model: best, effort: max}
|
|
57
|
+
quality_gate: {provider: runtime, model: best, effort: max}
|
|
58
|
+
execution: {provider: runtime, model: default, effort: high}
|
|
59
|
+
mechanical: {provider: runtime, model: fast, effort: low}
|
|
60
|
+
|
|
61
|
+
# ─── Fusion (Model Fabric PR-D) ─────────────────────────────────────────
|
|
62
|
+
# Panel answers in parallel; the judge analyses consensus, contradictions
|
|
63
|
+
# and blind spots, then synthesises. Empty panel = fusion disabled.
|
|
64
|
+
fusion:
|
|
65
|
+
judge: {provider: runtime, model: best, effort: max}
|
|
66
|
+
panel: []
|
|
67
|
+
# panel:
|
|
68
|
+
# - {provider: ollama, model: kimi-k2.6}
|
|
69
|
+
# - {provider: openrouter, model: deepseek/deepseek-v4-pro}
|
|
70
|
+
# - {provider: anthropic, model: best}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Fusion CLI — smoke path for the panel → judge pipeline.
|
|
2
|
+
|
|
3
|
+
Usage::
|
|
4
|
+
|
|
5
|
+
python -m core.fusion.cli "your question"
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from core.fusion.engine import FusionUnavailable, fuse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main(argv: list[str] | None = None) -> int:
|
|
16
|
+
args = argv if argv is not None else sys.argv[1:]
|
|
17
|
+
if not args:
|
|
18
|
+
print("usage: python -m core.fusion.cli \"question\"", file=sys.stderr)
|
|
19
|
+
return 1
|
|
20
|
+
prompt = " ".join(args)
|
|
21
|
+
try:
|
|
22
|
+
result = fuse(prompt)
|
|
23
|
+
except FusionUnavailable as exc:
|
|
24
|
+
print(f" ✗ {exc}", file=sys.stderr)
|
|
25
|
+
return 1
|
|
26
|
+
seats = ", ".join(
|
|
27
|
+
f"{a.provider}/{a.model}" + (" (failed)" if a.failed else "")
|
|
28
|
+
for a in result.answers
|
|
29
|
+
)
|
|
30
|
+
print(f"\n Fusion — judge {result.judge_provider}/{result.judge_model} "
|
|
31
|
+
f"| panel: {seats}\n")
|
|
32
|
+
print(result.text)
|
|
33
|
+
return 0
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Fusion engine — panel → judge → synthesis (Model Fabric PR-D).
|
|
2
|
+
|
|
3
|
+
Pattern validated by OpenRouter's DRACO results: a panel of diverse
|
|
4
|
+
models answers the same prompt in parallel; a judge model receives every
|
|
5
|
+
answer, analyses consensus, contradictions and blind spots, and writes
|
|
6
|
+
the final synthesis. A budget panel + frontier judge beats frontier solo
|
|
7
|
+
at roughly half the cost; self-fusion alone gains several points.
|
|
8
|
+
|
|
9
|
+
Configuration lives in ``models.yaml`` (``fusion.judge`` +
|
|
10
|
+
``fusion.panel`` — see ``core/runtime/model_router.py``). An empty panel
|
|
11
|
+
means fusion is disabled; callers get ``FusionUnavailable`` and fall
|
|
12
|
+
back to single-model completion.
|
|
13
|
+
|
|
14
|
+
Consumers: Forge complex/super tiers, QG adversarial review, and the
|
|
15
|
+
``python -m core.fusion.cli`` smoke path used by ``/arka-fusion``.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
21
|
+
from dataclasses import dataclass, field
|
|
22
|
+
|
|
23
|
+
from core.runtime.llm_provider import LLMResponse, LLMUnavailable
|
|
24
|
+
from core.runtime.model_router import ModelsConfig, RoleChoice, load_config
|
|
25
|
+
|
|
26
|
+
_PANEL_MAX_TOKENS = 2000
|
|
27
|
+
_JUDGE_MAX_TOKENS = 3000
|
|
28
|
+
|
|
29
|
+
_JUDGE_SYSTEM = (
|
|
30
|
+
"You are the judge in a model-fusion panel. You receive one question "
|
|
31
|
+
"and several independent answers from different models. Analyse them: "
|
|
32
|
+
"consensus points, contradictions, partial coverage, unique insights, "
|
|
33
|
+
"blind spots. Then write the best possible final answer grounded in "
|
|
34
|
+
"that analysis. Output ONLY the final answer — no meta-commentary "
|
|
35
|
+
"about the panel."
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class FusionUnavailable(RuntimeError):
|
|
40
|
+
"""Fusion cannot run: empty panel or no reachable participant."""
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(frozen=True)
|
|
44
|
+
class PanelAnswer:
|
|
45
|
+
provider: str
|
|
46
|
+
model: str
|
|
47
|
+
text: str
|
|
48
|
+
failed: bool = False
|
|
49
|
+
error: str = ""
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass(frozen=True)
|
|
53
|
+
class FusionResult:
|
|
54
|
+
text: str
|
|
55
|
+
judge_provider: str
|
|
56
|
+
judge_model: str
|
|
57
|
+
answers: list[PanelAnswer] = field(default_factory=list)
|
|
58
|
+
tokens_in: int = 0
|
|
59
|
+
tokens_out: int = 0
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _provider_for(choice: RoleChoice, config: ModelsConfig):
|
|
63
|
+
"""Instantiate the LLM provider for one panel/judge seat."""
|
|
64
|
+
from core.runtime.model_router import _resolve_alias
|
|
65
|
+
model = _resolve_alias(config, choice.provider, choice.model)
|
|
66
|
+
if choice.provider == "ollama":
|
|
67
|
+
from core.runtime.ollama_provider import OllamaProvider
|
|
68
|
+
return OllamaProvider(model=model), model
|
|
69
|
+
if choice.provider == "openrouter":
|
|
70
|
+
from core.runtime.openrouter_provider import OpenRouterProvider
|
|
71
|
+
return OpenRouterProvider(model=model), model
|
|
72
|
+
# runtime / anthropic / anything else: provider chain decides; the
|
|
73
|
+
# model override is advisory (subagent uses the session model).
|
|
74
|
+
from core.runtime.llm_provider import get_llm_provider
|
|
75
|
+
return get_llm_provider(), model
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _ask_participant(
|
|
79
|
+
choice: RoleChoice, config: ModelsConfig, prompt: str, system: str
|
|
80
|
+
) -> PanelAnswer:
|
|
81
|
+
try:
|
|
82
|
+
provider, model = _provider_for(choice, config)
|
|
83
|
+
response: LLMResponse = provider.complete(
|
|
84
|
+
prompt, max_tokens=_PANEL_MAX_TOKENS, system=system
|
|
85
|
+
)
|
|
86
|
+
if not response.text.strip():
|
|
87
|
+
return PanelAnswer(choice.provider, model, "", failed=True,
|
|
88
|
+
error="empty response")
|
|
89
|
+
return PanelAnswer(choice.provider, model, response.text)
|
|
90
|
+
except LLMUnavailable as exc:
|
|
91
|
+
return PanelAnswer(choice.provider, choice.model, "", failed=True,
|
|
92
|
+
error=str(exc))
|
|
93
|
+
except Exception as exc: # noqa: BLE001 — one dead seat must not kill the panel
|
|
94
|
+
return PanelAnswer(choice.provider, choice.model, "", failed=True,
|
|
95
|
+
error=f"unexpected: {exc}")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _judge_prompt(prompt: str, answers: list[PanelAnswer]) -> str:
|
|
99
|
+
blocks = []
|
|
100
|
+
for i, answer in enumerate(answers, 1):
|
|
101
|
+
blocks.append(
|
|
102
|
+
f"--- Answer {i} (model: {answer.model}) ---\n{answer.text}"
|
|
103
|
+
)
|
|
104
|
+
return (
|
|
105
|
+
f"QUESTION:\n{prompt}\n\n"
|
|
106
|
+
f"PANEL ANSWERS ({len(answers)}):\n\n" + "\n\n".join(blocks)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def fuse(
|
|
111
|
+
prompt: str,
|
|
112
|
+
*,
|
|
113
|
+
system: str = "",
|
|
114
|
+
config: ModelsConfig | None = None,
|
|
115
|
+
) -> FusionResult:
|
|
116
|
+
"""Run the full panel → judge → synthesis pipeline.
|
|
117
|
+
|
|
118
|
+
Raises FusionUnavailable when the panel is empty (fusion disabled in
|
|
119
|
+
models.yaml) or when every participant fails — the caller falls back
|
|
120
|
+
to a single-model completion, never to silence.
|
|
121
|
+
"""
|
|
122
|
+
if config is None:
|
|
123
|
+
config, _ = load_config()
|
|
124
|
+
panel = config.fusion.panel
|
|
125
|
+
if not panel:
|
|
126
|
+
raise FusionUnavailable(
|
|
127
|
+
"fusion panel is empty — configure fusion.panel in models.yaml "
|
|
128
|
+
"(try /arka-fusion for guided setup)"
|
|
129
|
+
)
|
|
130
|
+
with ThreadPoolExecutor(max_workers=max(len(panel), 1)) as pool:
|
|
131
|
+
answers = list(pool.map(
|
|
132
|
+
lambda choice: _ask_participant(choice, config, prompt, system),
|
|
133
|
+
panel,
|
|
134
|
+
))
|
|
135
|
+
alive = [a for a in answers if not a.failed]
|
|
136
|
+
if not alive:
|
|
137
|
+
details = "; ".join(f"{a.provider}/{a.model}: {a.error}" for a in answers)
|
|
138
|
+
raise FusionUnavailable(f"every panel participant failed — {details}")
|
|
139
|
+
|
|
140
|
+
judge_provider, judge_model = _provider_for(config.fusion.judge, config)
|
|
141
|
+
verdict = judge_provider.complete(
|
|
142
|
+
_judge_prompt(prompt, alive),
|
|
143
|
+
max_tokens=_JUDGE_MAX_TOKENS,
|
|
144
|
+
system=_JUDGE_SYSTEM,
|
|
145
|
+
)
|
|
146
|
+
return FusionResult(
|
|
147
|
+
text=verdict.text,
|
|
148
|
+
judge_provider=config.fusion.judge.provider,
|
|
149
|
+
judge_model=judge_model or verdict.model,
|
|
150
|
+
answers=answers,
|
|
151
|
+
tokens_in=verdict.tokens_in,
|
|
152
|
+
tokens_out=verdict.tokens_out,
|
|
153
|
+
)
|
|
Binary file
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Quality Gate redo-loop counter (excellence-mandate, v4.2.0).
|
|
2
|
+
|
|
3
|
+
The constitution caps REJECTED redo cycles at 2 — a third REJECTED
|
|
4
|
+
escalates to the operator with the full verdict instead of another
|
|
5
|
+
silent retry. This module is the mechanical counter behind that rule
|
|
6
|
+
(previously declarative only).
|
|
7
|
+
|
|
8
|
+
State: ``~/.arkaos/quality-gate/redo-counters.json`` keyed by session id.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
from dataclasses import dataclass
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
REDO_CAP = 2
|
|
18
|
+
|
|
19
|
+
_STATE_PATH = Path.home() / ".arkaos" / "quality-gate" / "redo-counters.json"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True)
|
|
23
|
+
class RedoState:
|
|
24
|
+
session_id: str
|
|
25
|
+
count: int
|
|
26
|
+
escalate: bool
|
|
27
|
+
|
|
28
|
+
def to_message(self) -> str:
|
|
29
|
+
if not self.escalate:
|
|
30
|
+
return (
|
|
31
|
+
f"[arka:qg] REJECTED — redo cycle {self.count}/{REDO_CAP}. "
|
|
32
|
+
f"Looping back to execution with the issue list."
|
|
33
|
+
)
|
|
34
|
+
return (
|
|
35
|
+
f"[arka:qg:escalate] REJECTED {self.count} times — cap of "
|
|
36
|
+
f"{REDO_CAP} redo cycles exceeded (excellence-mandate). "
|
|
37
|
+
f"STOP: present the full verdict to the operator and wait for "
|
|
38
|
+
f"a decision. Do not retry silently."
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _load(path: Path) -> dict:
|
|
43
|
+
try:
|
|
44
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
45
|
+
except (OSError, json.JSONDecodeError):
|
|
46
|
+
return {}
|
|
47
|
+
return data if isinstance(data, dict) else {}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def record_rejected(session_id: str, path: Path | None = None) -> RedoState:
|
|
51
|
+
"""Increment the REJECTED counter; escalate above the cap."""
|
|
52
|
+
state_path = path or _STATE_PATH
|
|
53
|
+
data = _load(state_path)
|
|
54
|
+
count = int(data.get(session_id, 0) or 0) + 1
|
|
55
|
+
data[session_id] = count
|
|
56
|
+
try:
|
|
57
|
+
state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
58
|
+
state_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
59
|
+
except OSError:
|
|
60
|
+
pass # counter must never block the gate itself
|
|
61
|
+
return RedoState(session_id=session_id, count=count,
|
|
62
|
+
escalate=count > REDO_CAP)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def reset(session_id: str, path: Path | None = None) -> None:
|
|
66
|
+
"""Clear the counter — called on APPROVED."""
|
|
67
|
+
state_path = path or _STATE_PATH
|
|
68
|
+
data = _load(state_path)
|
|
69
|
+
if session_id in data:
|
|
70
|
+
del data[session_id]
|
|
71
|
+
try:
|
|
72
|
+
state_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
73
|
+
except OSError:
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def current(session_id: str, path: Path | None = None) -> RedoState:
|
|
78
|
+
"""Read-only view of the counter."""
|
|
79
|
+
count = int(_load(path or _STATE_PATH).get(session_id, 0) or 0)
|
|
80
|
+
return RedoState(session_id=session_id, count=count,
|
|
81
|
+
escalate=count > REDO_CAP)
|
|
Binary file
|
|
Binary file
|