Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 33 additions & 11 deletions cmd/sin-code/chat_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@
fusionOnVerifyFail bool
fusionProviders string
fusionMaxCost float64
thinkingEnabled bool
thinkingBudget int
noTUI bool
watch string
}
Expand Down Expand Up @@ -149,6 +151,8 @@
sin-code chat --fusion-on-verify-fail enable SIN Fusion verify-tournament on verify.fail (issue #290)
sin-code chat --fusion-providers <list> override Fireworks models for the tournament (comma-separated)
sin-code chat --fusion-max-cost <usd> USD kill-switch per tournament invocation (default 5.0)
sin-code chat --thinking-enabled send thinking{type:"enabled"} on each request (per-provider reasoning budget)
sin-code chat --thinking-budget <n> per-request thinking.budget_tokens cap (0 = unbounded / provider default)
Oracle-mode fusion is experimental; set fusion.oracle_mode=true via config. Prefer PoC mode for verifiable tasks.`,
RunE: func(cmd *cobra.Command, args []string) error {
return runChat(cmd.Context(), opts)
Expand Down Expand Up @@ -179,6 +183,8 @@
f.BoolVar(&opts.fusionOnVerifyFail, "fusion-on-verify-fail", false, "enable SIN Fusion verify-tournament on verify.fail (issue #290)")
f.StringVar(&opts.fusionProviders, "fusion-providers", "", "comma-separated Fireworks model names for the tournament (e.g. minimax-m3,kimi-k2p7-code,glm-5p2)")
f.Float64Var(&opts.fusionMaxCost, "fusion-max-cost", 5.0, "USD kill-switch per tournament invocation (issue #290)")
f.BoolVar(&opts.thinkingEnabled, "thinking-enabled", false, "send thinking{type:\"enabled\"} on each LLM request (issue: thinking-budget-enforcement)")
f.IntVar(&opts.thinkingBudget, "thinking-budget", 0, "per-request thinking.budget_tokens cap (0 = unbounded; requires --thinking-enabled)")
f.BoolVar(&opts.noTUI, "no-tui", false, "skip TUI and use plain CLI loop")
f.StringVar(&opts.watch, "watch", "", "watch file patterns (comma-separated, e.g. *.go,*.py) and re-run the last prompt on change")
return cmd
Expand Down Expand Up @@ -210,10 +216,24 @@
sinCfg, _ := internal.LoadMergedConfig()

enableCache := sinCfg.LLMPromptCache
thinkingEnabled := opts.thinkingEnabled || sinCfg.LLMThinkingEnabled

Check failure on line 219 in cmd/sin-code/chat_cmd.go

View workflow job for this annotation

GitHub Actions / govulncheck

sinCfg.LLMThinkingEnabled undefined (type "github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal".SinCodeConfig has no field or method LLMThinkingEnabled)
thinkingBudget := opts.thinkingBudget
if thinkingBudget == 0 {
thinkingBudget = sinCfg.LLMThinkingBudget

Check failure on line 222 in cmd/sin-code/chat_cmd.go

View workflow job for this annotation

GitHub Actions / govulncheck

sinCfg.LLMThinkingBudget undefined (type "github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal".SinCodeConfig has no field or method LLMThinkingBudget)
}
thinkingCfg := &agentloop.ThinkingConfig{
Enabled: thinkingEnabled,
Budget: thinkingBudget,
}
completion := chatNewProviderCompletionFn(client, model, agentCfg.MaxTokens, agentCfg.Temperature)
if enableCache {
cache := llm.NewPromptCache(llm.DefaultCacheTTL)
completion = agentloop.NewProviderCompletionWithCache(client, model, agentCfg.MaxTokens, agentCfg.Temperature, cache)
completion = agentloop.NewProviderCompletionFull(client, model, agentCfg.MaxTokens, agentCfg.Temperature, cache, thinkingCfg)
} else if thinkingCfg.Enabled {
// Thinking-budget requires the *Full constructor so the
// thinking{type:"enabled"} block ends up on the wire. With
// the legacy factories the request body would not carry it.
completion = agentloop.NewProviderCompletionFull(client, model, agentCfg.MaxTokens, agentCfg.Temperature, nil, thinkingCfg)
}

perm := permission.New(chatRulesForAgentFn(agentCfg))
Expand Down Expand Up @@ -393,16 +413,18 @@
}

loop := &agentloop.Loop{
Gate: gate,
LocalTool: combinedTool(workspace, mcpMgr),
LocalSpec: combinedSpecs(mcpMgr),
Workspace: workspace,
MaxTurns: opts.maxTurns,
SessionID: sess.ID,
Completion: completion,
Hooks: hookEngine,
Perm: perm,
Ask: ask,
Gate: gate,
LocalTool: combinedTool(workspace, mcpMgr),
LocalSpec: combinedSpecs(mcpMgr),
Workspace: workspace,
MaxTurns: opts.maxTurns,
SessionID: sess.ID,
Completion: completion,
Hooks: hookEngine,
Perm: perm,
Ask: ask,
ThinkingEnabled: thinkingCfg.Enabled,
ThinkingBudgetPerRequest: thinkingCfg.Budget,
}

// Apply config-file defaults for tool coverage (issue #248) and merge
Expand Down
131 changes: 131 additions & 0 deletions cmd/sin-code/fusion_cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-License-Identifier: MIT
// Purpose: `sin-code fusion` — SIN Fusion v1 status/config subcommand (issue #290).
// Read-only: shows tournament configuration, provider pool, and env var overrides.
package main

import (
"fmt"
"os"
"strings"

"github.com/spf13/cobra"

"github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config"

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / spec check

no required module provides package github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config; to add it:

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / Profile verify (issue

no required module provides package github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config; to add it:

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / govulncheck

could not import github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config (invalid package name: "")

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / govulncheck

no required module provides package github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config; to add it:

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / verify

no required module provides package github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config; to add it:

Check failure on line 13 in cmd/sin-code/fusion_cmd.go

View workflow job for this annotation

GitHub Actions / go test

no required module provides package github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/config; to add it:
"github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/fusion"
)

func NewFusionCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "fusion",
Short: "SIN Fusion v1 verify-tournament status and config",
Long: `sin-code fusion shows the configuration and provider pool for the
SIN Fusion v1 verify-tournament (issue #290). When the verify-gate (M3) fails,
fusion fans out to N Fireworks models in parallel; first PoC-pass wins.
Oracle mode (issue #344) runs all candidates and uses an LLM judge.

All subcommands are read-only — no side effects, no API calls.`,
}
cmd.AddCommand(newFusionStatusCmd())
cmd.AddCommand(newFusionConfigCmd())
cmd.AddCommand(newFusionProvidersCmd())
return cmd
}

func newFusionStatusCmd() *cobra.Command {
return &cobra.Command{
Use: "status",
Short: "Show fusion enabled/disabled status and gate mode",
RunE: func(_ *cobra.Command, _ []string) error {
cfg, _ := config.LoadMergedConfig()
fmt.Println("SIN Fusion v1 — Status")
fmt.Println(strings.Repeat("─", 40))
fmt.Printf(" Enabled: %v\n", cfg.FusionEnabled)
fmt.Printf(" Oracle mode: %v\n", cfg.FusionOracleMode)
fmt.Printf(" Difficulty gate: %v\n", cfg.FusionDifficultyGate)
fmt.Printf(" Max cost (USD): %.2f\n", cfg.FusionMaxCostUSD)
fmt.Printf(" Min quorum: %d\n", cfg.FusionMinQuorum)
fmt.Printf(" Per-provider TO: %ds\n", cfg.FusionPerProviderTimeoutS)
provStr := ""
if len(cfg.FusionProviders) > 0 {
provStr = strings.Join(cfg.FusionProviders, ",")
}
providers := fusion.LoadFireworksPool(nil, provStr)
fmt.Printf(" Providers loaded: %d\n", len(providers))
if evalModel := os.Getenv("SIN_EVALUATOR_MODEL"); evalModel != "" {
fmt.Printf(" Evaluator model: %s (SIN_EVALUATOR_MODEL)\n", evalModel)
} else {
fmt.Println(" Evaluator model: (worker model fallback)")
}
return nil
},
}
}

func newFusionConfigCmd() *cobra.Command {
return &cobra.Command{
Use: "config",
Short: "Show full fusion configuration including env var overrides",
RunE: func(_ *cobra.Command, _ []string) error {
cfg, _ := config.LoadMergedConfig()
fmt.Println("SIN Fusion v1 — Configuration")
fmt.Println(strings.Repeat("─", 40))
fmt.Println(" Config keys (sin-code.toml):")
fmt.Printf(" fusion.enabled: %v\n", cfg.FusionEnabled)
fmt.Printf(" fusion.oracle_mode: %v\n", cfg.FusionOracleMode)
fmt.Printf(" fusion.difficulty_gate: %v\n", cfg.FusionDifficultyGate)
fmt.Printf(" fusion.max_cost_usd: %.2f\n", cfg.FusionMaxCostUSD)
fmt.Printf(" fusion.min_quorum: %d\n", cfg.FusionMinQuorum)
fmt.Printf(" fusion.per_provider_timeout_s: %d\n", cfg.FusionPerProviderTimeoutS)
if len(cfg.FusionProviders) > 0 {
fmt.Printf(" fusion.providers: %s\n", strings.Join(cfg.FusionProviders, ", "))
} else {
fmt.Println(" fusion.providers: (default 6-model pool)")
}
fmt.Println()
fmt.Println(" Environment overrides:")
printEnvVar("SIN_EVALUATOR_MODEL", "")
printEnvVar("SIN_EVALUATOR_BASE_URL", "")
printEnvVar("SIN_EVALUATOR_API_KEY", "(masked)")
return nil
},
}
}

func newFusionProvidersCmd() *cobra.Command {
return &cobra.Command{
Use: "providers",
Short: "List the Fireworks pool providers (model, base URL, max tokens)",
RunE: func(_ *cobra.Command, _ []string) error {
cfg, _ := config.LoadMergedConfig()
provStr := ""
if len(cfg.FusionProviders) > 0 {
provStr = strings.Join(cfg.FusionProviders, ",")
}
providers := fusion.LoadFireworksPool(nil, provStr)
fmt.Println("SIN Fusion v1 — Provider Pool")
fmt.Println(strings.Repeat("─", 40))
if len(providers) == 0 {
fmt.Println(" No providers loaded (check fusion.providers config or FIREWORKS_API_KEY)")
return nil
}
fmt.Printf(" %-30s %-40s %s\n", "MODEL", "BASE URL", "MAX TOKENS")
fmt.Printf(" %s %s %s\n", strings.Repeat("─", 30), strings.Repeat("─", 40), strings.Repeat("─", 10))
for _, p := range providers {
fmt.Printf(" %-30s %-40s %d\n", p.Model, p.BaseURL, p.MaxTokens)
}
fmt.Printf("\n Total: %d providers\n", len(providers))
return nil
},
}
}

func printEnvVar(key, mask string) {
val := os.Getenv(key)
if val == "" {
fmt.Printf(" %s: (not set)\n", key)
} else if mask != "" {
fmt.Printf(" %s: %s\n", key, mask)
} else {
fmt.Printf(" %s: %s\n", key, val)
}
}
148 changes: 148 additions & 0 deletions cmd/sin-code/internal/agentloop/compaction_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
// Package agentloop - context compaction types (CompactInput / CompactResult /
// ContextCompactionMode / CompactionTrigger / CompactorConfig). See
// compaction.go for the implementation, this file holds the public surface
// only.
package agentloop

import "github.com/OpenSIN-Code/SIN-Code/cmd/sin-code/internal/session"

// ContextCompactionMode selects the compaction algorithm.
// Empty == "off". Closed set: off | deterministic | llm | hybrid.
type ContextCompactionMode string

const (
ContextCompactionOff ContextCompactionMode = "off"
ContextCompactionDeterministic ContextCompactionMode = "deterministic"
ContextCompactionLLM ContextCompactionMode = "llm"
ContextCompactionHybrid ContextCompactionMode = "hybrid"
)

// ParseContextCompactionMode normalises user input. Empty/dotted variants
// map to Off; unknown values return a typed error.
func ParseContextCompactionMode(s string) (ContextCompactionMode, error) {
switch toLowerTrim(s) {

Check failure on line 24 in cmd/sin-code/internal/agentloop/compaction_types.go

View workflow job for this annotation

GitHub Actions / govulncheck

undefined: toLowerTrim
case "off", "none", "disabled", "", "default":
return ContextCompactionOff, nil
case "deterministic", "det":
return ContextCompactionDeterministic, nil
case "llm", "summarize":
return ContextCompactionLLM, nil
case "hybrid", "llm+deterministic":
return ContextCompactionHybrid, nil
}
return ContextCompactionOff, errUnknownMode(s)

Check failure on line 34 in cmd/sin-code/internal/agentloop/compaction_types.go

View workflow job for this annotation

GitHub Actions / govulncheck

undefined: errUnknownMode
}

// String makes ContextCompactionMode satisfy fmt.Stringer.
func (m ContextCompactionMode) String() string {
if m == "" {
return string(ContextCompactionOff)
}
return string(m)
}

// IsLossy reports whether the mode produces sidecar-snapshot-worthy output.
func (m ContextCompactionMode) IsLossy() bool {
switch m {
case ContextCompactionLLM, ContextCompactionHybrid:
return true
}
return false
}

// CompactionTrigger decides when ShouldCompact returns true.
type CompactionTrigger string

const (
CompactionTriggerTurns CompactionTrigger = "turns"
CompactionTriggerTokens CompactionTrigger = "tokens"
CompactionTriggerBoth CompactionTrigger = "both"
)

// ParseCompactionTrigger normalises user input.
func ParseCompactionTrigger(s string) (CompactionTrigger, error) {
switch toLowerTrim(s) {

Check failure on line 65 in cmd/sin-code/internal/agentloop/compaction_types.go

View workflow job for this annotation

GitHub Actions / govulncheck

undefined: toLowerTrim
case "turns", "messages":
return CompactionTriggerTurns, nil
case "tokens":
return CompactionTriggerTokens, nil
case "", "both", "any", "default":
return CompactionTriggerBoth, nil
}
return CompactionTriggerBoth, errUnknownTrigger(s)

Check failure on line 73 in cmd/sin-code/internal/agentloop/compaction_types.go

View workflow job for this annotation

GitHub Actions / govulncheck

undefined: errUnknownTrigger
}

// String makes CompactionTrigger satisfy fmt.Stringer.
func (t CompactionTrigger) String() string {
if t == "" {
return string(CompactionTriggerBoth)
}
return string(t)
}

// CompactorConfig is the wired shape the loopbuilder passes.
type CompactorConfig struct {
Mode ContextCompactionMode
Trigger CompactionTrigger
Threshold float64
ContextWindow int
MaxTokens int
PreserveEvidence bool
RecentTurns int
}

// DefaultCompactorConfig returns the safe default config (Mode=off so
// the legacy single-gate behavior is preserved byte-for-byte).
func DefaultCompactorConfig() CompactorConfig {
return CompactorConfig{
Mode: ContextCompactionOff,
Trigger: CompactionTriggerBoth,
Threshold: 0.8,
ContextWindow: 0,
MaxTokens: 8000,
PreserveEvidence: true,
RecentTurns: 4,
}
}

// Normalize fills zero-value fields with safe defaults and clamps bad
// inputs so downstream code never has to re-validate.
func (c *CompactorConfig) Normalize() {
if c.Mode == "" {
c.Mode = ContextCompactionOff
}
if c.Trigger == "" {
c.Trigger = CompactionTriggerBoth
}
if c.Threshold <= 0 {
c.Threshold = 0.8
}
if c.MaxTokens <= 0 {
c.MaxTokens = 8000
}
if c.RecentTurns <= 0 {
c.RecentTurns = 4
}
}

// CompactInput is the request payload for CompactInput(ctx, input).
type CompactInput struct {
Messages []session.Message
EvidenceIndices map[int]bool
Strategy CompactionStrategy
Mode ContextCompactionMode
MaxTokens int
SessionID string
}

// CompactResult is the structured response from CompactInput.
type CompactResult struct {
Kept []session.Message
Dropped []session.Message
Summary string
SnapshotID string
TokensBefore int
TokensAfter int
Mode ContextCompactionMode
}
Loading
Loading