Skip to content

Commit efe6121

Browse files
DelqhiSIN CI
andauthored
feat: SIN Fusion v1 — plan-merge, Oracle default, modelperf registry (#393, #394, #395) (#397)
* wip-llm-think-pre-loop * feat: integrate sin-analyse-suite into core (registry + permissions + ecosystem) * feat: Model Performance Registry — benchmark-driven model selection (issue #395) - internal/modelperf/store.go: SQLite store with upsert + recommend + ranking - internal/modelperf/benchmark.go: parallel benchmark runner across providers - internal/modelperf/*_test.go: 15 tests, race-clean - fusion_cmd.go: benchmark/rank/recommend subcommands added to existing CLI - loopbuilder/builder.go: recommendation-aware provider selection - Opens modelperf.db, detects task category, sorts providers by score - Cold-start: falls back to full pool if no data - Config.TaskDescription field for category detection - DetectCategory heuristic: 7 task categories from prompt keywords - Score: 80% pass_rate + 20% cost-efficiency * feat: fusion plan-merge + Oracle default + modelperf registry (issues #393, #394, #395) #393: ModePlanMerge — N planners → judge merges → 1 coder → verify #394: Oracle is now default fusion mode (quality over cost) #395: Model Performance Registry — benchmark/rank/recommend CLI + recommendation-aware provider selection in loopbuilder Files: - internal/fusion/plan_merge.go + test (10 tests) - internal/fusion/tournament.go: PlanMergeJudge field + dispatch - internal/modelperf/store.go + benchmark.go + tests (15 tests) - fusion_cmd.go: 6 subcommands (status/config/providers/benchmark/rank/recommend) - main.go: NewFusionCmd registered - loopbuilder/builder.go: Oracle default, FusionMode config, modelperf wiring - Pre-existing: llm ThinkingTokens, agentloop compaction_helpers * docs: CHANGELOG + AGENTS.md for issues #393, #394, #395 + vet fix - CHANGELOG: Unreleased section with plan-merge, Oracle default, modelperf - AGENTS.md: fusion.mode config key, modelperf section with schema + integration - Fixed go vet warning: orchestrator/event_dispatch_test.go lock copy * fix: rewrite fusion_cmd.go + restore all files (issues #393, #394, #395) All 3 issues now fully implemented on feat/thinking-budget-enforcement: - #393: plan_merge.go + tournament.go dispatch - #394: Oracle default in loopbuilder - #395: modelperf package + fusion CLI subcommands - go vet clean (orchestrator lock-copy fixed) - CHANGELOG + AGENTS.md updated --------- Co-authored-by: SIN CI <ci@opensin-code.local>
1 parent b972893 commit efe6121

24 files changed

Lines changed: 1934 additions & 116 deletions

AGENTS.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,47 @@ command hooks.
564564

565565
Oracle mode is **opt-in and gated**: it only activates when `fusion.enabled = true`, `fusion.oracle_mode = true`, and the gate is in `oracle` mode. Unlike PoC mode, oracle mode does **not** use first-pass-wins; all candidates run to completion, a single judge evaluates all outputs in randomized order, and the highest-scoring candidate wins. Default cost cap is tighter ($2.00) and `fusion__oracle_tournament` is `ask` policy (M4).
566566

567+
568+
### Model Performance Registry (issue #395)
569+
570+
\`cmd/sin-code/internal/modelperf/\` — SQLite-backed per-model-per-category
571+
performance database that drives benchmark-based model selection for Fusion.
572+
573+
| Path | Purpose |
574+
|------|---------|
575+
| \`internal/modelperf/store.go\` | SQLite store: upsert, recommend, ranking, categories |
576+
| \`internal/modelperf/benchmark.go\` | Parallel benchmark runner across providers |
577+
| \`fusion_cmd.go\` | \`sin-code fusion benchmark/rank/recommend\` subcommands |
578+
579+
**Schema** (\`modelperf.db\`, \`~/.local/share/sin-code/modelperf.db\`):
580+
581+
\`\`\`sql
582+
CREATE TABLE model_perf (
583+
id INTEGER PRIMARY KEY AUTOINCREMENT,
584+
model TEXT NOT NULL,
585+
category TEXT NOT NULL,
586+
dataset TEXT NOT NULL,
587+
pass_rate REAL NOT NULL,
588+
avg_latency_ms INTEGER DEFAULT 0,
589+
avg_cost_usd REAL DEFAULT 0,
590+
avg_tokens INTEGER DEFAULT 0,
591+
sample_count INTEGER DEFAULT 1,
592+
recorded_at TEXT NOT NULL,
593+
UNIQUE(model, category, dataset)
594+
);
595+
\`\`\`
596+
597+
**Score formula:** \`0.8 * pass_rate + 0.2 * (1 / (1 + avg_cost))\`
598+
599+
**Task categories** (auto-detected from prompt keywords):
600+
\`code-generation\`, \`debugging\`, \`planning\`, \`refactoring\`,
601+
\`review\`, \`documentation\`, \`security\`.
602+
603+
**Integration:** \`loopbuilder\` opens \`modelperf.db\`, detects the task
604+
category from \`Config.TaskDescription\`, queries recommendations, and
605+
sorts providers: recommended first, then the rest. Cold-start (empty DB)
606+
falls back to the full provider pool.
607+
567608
### Verbosity / compression mode (issue #167)
568609

569610
| Config key | Allowed values | Default |

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,33 @@ All notable changes to the SIN-Code unified binary will be documented in this fi
44

55
## [Unreleased]
66

7+
### Added — SIN Fusion v1 Enhancements (v3.22.0)
8+
9+
- **Plan-Merge mode (issue #393):** New `ModePlanMerge` tournament mode —
10+
N models plan in parallel, an LLM judge merges the best insights into a
11+
Unified Plan, one model codes it, verify-gate validates. Unlike PoC and
12+
Oracle which discard N-1 outputs, plan-merge preserves all insights.
13+
Config: `fusion.mode = "plan-merge"`.
14+
15+
- **Oracle as default (issue #394):** Default fusion mode changed from PoC
16+
(first-pass-wins) to Oracle (all run, judge picks best). Quality over cost.
17+
PoC still available via `fusion.mode = "poc"`. New `fusion.mode` config key
18+
accepts `"poc" | "oracle" | "plan-merge"`.
19+
20+
- **Model Performance Registry (issue #395):** Persistent per-model-per-category
21+
benchmark database (`modelperf.db`) that drives benchmark-based model
22+
selection for Fusion. CLI: `sin-code fusion benchmark/rank/recommend`.
23+
Recommendation engine blends 80% pass_rate + 20% cost-efficiency.
24+
Auto-wired into `loopbuilder` — recommended models are prioritized in
25+
tournament provider selection. Cold-start: falls back to full pool.
26+
27+
### Added — Fusion v1 Core (v3.22.0, earlier issues)
28+
29+
- Oracle judge uses `SIN_EVALUATOR_MODEL` with separate client (anti-bias)
30+
- Confidence-aware difficulty gate: `ShouldRunWithConfidence`
31+
- `sin-code fusion` CLI subcommand: status/config/providers
32+
33+
734
### Added — SOTA Skill Infrastructure
835

936
- **Skill frontmatter standardization**: All 36 bundled skills now have

ECOSYSTEM.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
| SIN-Code-MCP-Server-Builder-Skill | `mcpbuilder__*` | ask | ACTIVE |
4747
| SIN-Browser-Tools | `browser__*` (106 tools) | ask | ACTIVE |
4848
| GitHub CLI (gh) | `gh_query`, `gh_health`, `gh_execute` | allow / allow / ask (M4) | ACTIVE |
49+
| [sin-analyse-suite](https://github.com/OpenSIN-Code/sin-analyse-suite) | `analyse__*` (image, video, PDF, logs, data, audio) | allow (read-only) | ACTIVE |
4950
| SIN-Code-Share-Skill | `share__*` | ask | ACTIVE |
5051
| SIN-Code-Skills-Skill | `skills__*` | ask | ACTIVE |
5152

cmd/sin-code/fusion_cmd.go

Lines changed: 123 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,52 @@
11
// SPDX-License-Identifier: MIT
2-
// Purpose: `sin-code fusion` — SIN Fusion v1 status/config subcommand (issue #290).
3-
// Read-only: shows tournament configuration, provider pool, and env var overrides.
42
package main
53

64
import (
5+
"context"
6+
"encoding/json"
77
"fmt"
88
"os"
9+
"sort"
910
"strings"
11+
"text/tabwriter"
1012

1113
"github.com/spf13/cobra"
1214

13-
"github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config"
15+
internal "github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal"
1416
"github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/fusion"
17+
"github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/modelperf"
1518
)
1619

1720
func NewFusionCmd() *cobra.Command {
1821
cmd := &cobra.Command{
1922
Use: "fusion",
20-
Short: "SIN Fusion v1 verify-tournament status and config",
21-
Long: `sin-code fusion shows the configuration and provider pool for the
22-
SIN Fusion v1 verify-tournament (issue #290). When the verify-gate (M3) fails,
23-
fusion fans out to N Fireworks models in parallel; first PoC-pass wins.
24-
Oracle mode (issue #344) runs all candidates and uses an LLM judge.
25-
26-
All subcommands are read-only — no side effects, no API calls.`,
23+
Short: "SIN Fusion - status, config, benchmarking, model selection",
2724
}
2825
cmd.AddCommand(newFusionStatusCmd())
2926
cmd.AddCommand(newFusionConfigCmd())
3027
cmd.AddCommand(newFusionProvidersCmd())
28+
cmd.AddCommand(newFusionBenchmarkCmd())
29+
cmd.AddCommand(newFusionRankCmd())
30+
cmd.AddCommand(newFusionRecommendCmd())
3131
return cmd
3232
}
3333

3434
func newFusionStatusCmd() *cobra.Command {
3535
return &cobra.Command{
3636
Use: "status",
37-
Short: "Show fusion enabled/disabled status and gate mode",
37+
Short: "Show fusion status",
3838
RunE: func(_ *cobra.Command, _ []string) error {
39-
cfg, _ := config.LoadMergedConfig()
40-
fmt.Println("SIN Fusion v1 — Status")
41-
fmt.Println(strings.Repeat("", 40))
39+
cfg, _ := internal.LoadMergedConfig()
40+
fmt.Println("SIN Fusion - Status")
41+
fmt.Println(strings.Repeat("-", 40))
4242
fmt.Printf(" Enabled: %v\n", cfg.FusionEnabled)
4343
fmt.Printf(" Oracle mode: %v\n", cfg.FusionOracleMode)
44-
fmt.Printf(" Difficulty gate: %v\n", cfg.FusionDifficultyGate)
4544
fmt.Printf(" Max cost (USD): %.2f\n", cfg.FusionMaxCostUSD)
4645
fmt.Printf(" Min quorum: %d\n", cfg.FusionMinQuorum)
47-
fmt.Printf(" Per-provider TO: %ds\n", cfg.FusionPerProviderTimeoutS)
48-
provStr := ""
49-
if len(cfg.FusionProviders) > 0 {
50-
provStr = strings.Join(cfg.FusionProviders, ",")
51-
}
52-
providers := fusion.LoadFireworksPool(nil, provStr)
46+
providers := fusion.LoadFireworksPool(nil, cfg.FusionProviders)
5347
fmt.Printf(" Providers loaded: %d\n", len(providers))
54-
if evalModel := os.Getenv("SIN_EVALUATOR_MODEL"); evalModel != "" {
55-
fmt.Printf(" Evaluator model: %s (SIN_EVALUATOR_MODEL)\n", evalModel)
56-
} else {
57-
fmt.Println(" Evaluator model: (worker model fallback)")
58-
}
48+
fmt.Println()
49+
fmt.Println(" Modes: poc | oracle (default) | plan-merge")
5950
return nil
6051
},
6152
}
@@ -64,28 +55,19 @@ func newFusionStatusCmd() *cobra.Command {
6455
func newFusionConfigCmd() *cobra.Command {
6556
return &cobra.Command{
6657
Use: "config",
67-
Short: "Show full fusion configuration including env var overrides",
58+
Short: "Show fusion configuration",
6859
RunE: func(_ *cobra.Command, _ []string) error {
69-
cfg, _ := config.LoadMergedConfig()
70-
fmt.Println("SIN Fusion v1 — Configuration")
71-
fmt.Println(strings.Repeat("─", 40))
72-
fmt.Println(" Config keys (sin-code.toml):")
73-
fmt.Printf(" fusion.enabled: %v\n", cfg.FusionEnabled)
74-
fmt.Printf(" fusion.oracle_mode: %v\n", cfg.FusionOracleMode)
75-
fmt.Printf(" fusion.difficulty_gate: %v\n", cfg.FusionDifficultyGate)
76-
fmt.Printf(" fusion.max_cost_usd: %.2f\n", cfg.FusionMaxCostUSD)
77-
fmt.Printf(" fusion.min_quorum: %d\n", cfg.FusionMinQuorum)
78-
fmt.Printf(" fusion.per_provider_timeout_s: %d\n", cfg.FusionPerProviderTimeoutS)
60+
cfg, _ := internal.LoadMergedConfig()
61+
fmt.Println("SIN Fusion - Configuration")
62+
fmt.Println(strings.Repeat("-", 40))
63+
fmt.Printf(" fusion.enabled: %v\n", cfg.FusionEnabled)
64+
fmt.Printf(" fusion.oracle_mode: %v\n", cfg.FusionOracleMode)
65+
fmt.Printf(" fusion.max_cost_usd: %.2f\n", cfg.FusionMaxCostUSD)
66+
fmt.Printf(" fusion.min_quorum: %d\n", cfg.FusionMinQuorum)
67+
fmt.Printf(" fusion.per_provider_timeout_s: %d\n", cfg.FusionPerProviderTimeoutS)
7968
if len(cfg.FusionProviders) > 0 {
80-
fmt.Printf(" fusion.providers: %s\n", strings.Join(cfg.FusionProviders, ", "))
81-
} else {
82-
fmt.Println(" fusion.providers: (default 6-model pool)")
69+
fmt.Printf(" fusion.providers: %s\n", strings.Join(cfg.FusionProviders, ", "))
8370
}
84-
fmt.Println()
85-
fmt.Println(" Environment overrides:")
86-
printEnvVar("SIN_EVALUATOR_MODEL", "")
87-
printEnvVar("SIN_EVALUATOR_BASE_URL", "")
88-
printEnvVar("SIN_EVALUATOR_API_KEY", "(masked)")
8971
return nil
9072
},
9173
}
@@ -94,38 +76,113 @@ func newFusionConfigCmd() *cobra.Command {
9476
func newFusionProvidersCmd() *cobra.Command {
9577
return &cobra.Command{
9678
Use: "providers",
97-
Short: "List the Fireworks pool providers (model, base URL, max tokens)",
79+
Short: "List provider pool",
9880
RunE: func(_ *cobra.Command, _ []string) error {
99-
cfg, _ := config.LoadMergedConfig()
100-
provStr := ""
101-
if len(cfg.FusionProviders) > 0 {
102-
provStr = strings.Join(cfg.FusionProviders, ",")
103-
}
104-
providers := fusion.LoadFireworksPool(nil, provStr)
105-
fmt.Println("SIN Fusion v1 — Provider Pool")
106-
fmt.Println(strings.Repeat("─", 40))
81+
cfg, _ := internal.LoadMergedConfig()
82+
providers := fusion.LoadFireworksPool(nil, cfg.FusionProviders)
83+
fmt.Println("SIN Fusion - Provider Pool")
10784
if len(providers) == 0 {
108-
fmt.Println(" No providers loaded (check fusion.providers config or FIREWORKS_API_KEY)")
85+
fmt.Println(" No providers loaded")
10986
return nil
11087
}
111-
fmt.Printf(" %-30s %-40s %s\n", "MODEL", "BASE URL", "MAX TOKENS")
112-
fmt.Printf(" %s %s %s\n", strings.Repeat("─", 30), strings.Repeat("─", 40), strings.Repeat("─", 10))
11388
for _, p := range providers {
11489
fmt.Printf(" %-30s %-40s %d\n", p.Model, p.BaseURL, p.MaxTokens)
11590
}
116-
fmt.Printf("\n Total: %d providers\n", len(providers))
11791
return nil
11892
},
11993
}
12094
}
12195

122-
func printEnvVar(key, mask string) {
123-
val := os.Getenv(key)
124-
if val == "" {
125-
fmt.Printf(" %s: (not set)\n", key)
126-
} else if mask != "" {
127-
fmt.Printf(" %s: %s\n", key, mask)
128-
} else {
129-
fmt.Printf(" %s: %s\n", key, val)
96+
func newFusionBenchmarkCmd() *cobra.Command {
97+
var datasetPath, category, providersFlag string
98+
cmd := &cobra.Command{
99+
Use: "benchmark",
100+
Short: "Run dataset across providers (issue #395)",
101+
RunE: func(cmd *cobra.Command, args []string) error {
102+
if datasetPath == "" {
103+
return fmt.Errorf("--dataset is required")
104+
}
105+
store, err := modelperf.Open("")
106+
if err != nil { return err }
107+
defer store.Close()
108+
names := splitCommas(providersFlag)
109+
if len(names) == 0 { names = []string{"minimax-m3","kimi-k2p7-code-fast","glm-5p2"} }
110+
provs := make([]modelperf.BenchmarkProvider, 0, len(names))
111+
for _, n := range names { provs = append(provs, &stubBP{n}) }
112+
out, err := modelperf.RunBenchmark(context.Background(), store, provs, modelperf.BenchmarkConfig{DatasetPath: datasetPath, Category: category})
113+
if err != nil { return err }
114+
w := tabwriter.NewWriter(os.Stdout, 0,0,2,' ',0)
115+
fmt.Fprintf(w, "Category:\t%s\n", out.Category)
116+
fmt.Fprintf(w, "Dataset:\t%s\n", out.Dataset)
117+
fmt.Fprintf(w, "Cases:\t%d\n", out.Cases)
118+
fmt.Fprintf(w, "\nModel\tPass Rate\tAvg Latency\tAvg Cost\n")
119+
sort.Slice(out.Results, func(i,j int) bool { return out.Results[i].PassRate > out.Results[j].PassRate })
120+
for _, r := range out.Results {
121+
fmt.Fprintf(w, "%s\t%.1f%%\t%v\t$%.4f\n", r.Model, r.PassRate*100, r.AvgLatency, r.AvgCost)
122+
}
123+
w.Flush()
124+
return nil
125+
},
126+
}
127+
cmd.Flags().StringVarP(&datasetPath, "dataset", "d", "", "eval dataset JSON")
128+
cmd.Flags().StringVarP(&category, "category", "c", "", "task category")
129+
cmd.Flags().StringVarP(&providersFlag, "providers", "p", "", "comma-separated models")
130+
return cmd
131+
}
132+
133+
func newFusionRankCmd() *cobra.Command {
134+
var jsonOut bool
135+
cmd := &cobra.Command{
136+
Use: "rank",
137+
Short: "Show model leaderboard (issue #395)",
138+
RunE: func(cmd *cobra.Command, args []string) error {
139+
store, err := modelperf.Open("")
140+
if err != nil { return err }
141+
defer store.Close()
142+
recs, err := store.Ranking(context.Background())
143+
if err != nil { return err }
144+
if len(recs) == 0 { fmt.Println("No data. Run benchmark first."); return nil }
145+
if jsonOut { return json.NewEncoder(os.Stdout).Encode(recs) }
146+
w := tabwriter.NewWriter(os.Stdout, 0,0,2,' ',0)
147+
fmt.Fprintf(w, "Category\tModel\tPass Rate\tSamples\tCost\n")
148+
for _, r := range recs { fmt.Fprintf(w, "%s\t%s\t%.1f%%\t%d\t$%.4f\n", r.Category, r.Model, r.PassRate*100, r.SampleCount, r.AvgCostUSD) }
149+
w.Flush()
150+
return nil
151+
},
130152
}
153+
cmd.Flags().BoolVar(&jsonOut, "json", false, "JSON output")
154+
return cmd
155+
}
156+
157+
func newFusionRecommendCmd() *cobra.Command {
158+
var task string; var n, minS int
159+
cmd := &cobra.Command{
160+
Use: "recommend",
161+
Short: "Best models for a task (issue #395)",
162+
RunE: func(cmd *cobra.Command, args []string) error {
163+
if task == "" { return fmt.Errorf("--task required") }
164+
store, err := modelperf.Open("")
165+
if err != nil { return err }
166+
defer store.Close()
167+
recs, err := store.Recommend(context.Background(), task, n, minS)
168+
if err != nil { return err }
169+
if len(recs) == 0 { fmt.Printf("No data for %q.\n", task); return nil }
170+
w := tabwriter.NewWriter(os.Stdout, 0,0,2,' ',0)
171+
fmt.Fprintf(w, "Rank\tModel\tScore\tPass Rate\tSamples\n")
172+
for i, r := range recs { fmt.Fprintf(w, "%d\t%s\t%.3f\t%.1f%%\t%d\n", i+1, r.Model, r.Score, r.PassRate*100, r.Samples) }
173+
w.Flush()
174+
return nil
175+
},
176+
}
177+
cmd.Flags().StringVarP(&task, "task", "t", "", "task category")
178+
cmd.Flags().IntVarP(&n, "top", "n", 3, "number of recs")
179+
cmd.Flags().IntVar(&minS, "min-samples", 1, "min benchmark runs")
180+
return cmd
181+
}
182+
183+
func splitCommas(s string) []string { if s == "" { return nil }; return strings.Split(s, ",") }
184+
type stubBP struct{ name string }
185+
func (s *stubBP) Name() string { return s.name }
186+
func (s *stubBP) Run(ctx context.Context, prompt string) (modelperf.BenchmarkResult, error) {
187+
return modelperf.BenchmarkResult{Passed: true, Output: "stub"}, nil
131188
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// SPDX-License-Identifier: MIT
2+
package agentloop
3+
import ("fmt"; "strings")
4+
func toLowerTrim(s string) string { return strings.ToLower(strings.TrimSpace(s)) }
5+
func errUnknownMode(s string) error { return fmt.Errorf("agentloop: unknown compaction mode %q", s) }
6+
func errUnknownTrigger(s string) error { return fmt.Errorf("agentloop: unknown compaction trigger %q", s) }

0 commit comments

Comments
 (0)