Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
630e040
Define model-aware reasoning capabilities
nvdorman Aug 12, 2026
4388c43
Redact unsupported reasoning overrides
nvdorman Aug 12, 2026
6903104
Honor model-specific reasoning controls
nvdorman Aug 12, 2026
0bf0314
Fix pre-request reasoning validation gaps found in Task 2 review
nvdorman Aug 12, 2026
d877863
Close two correctness gaps in Gemini reasoning mapping
nvdorman Aug 12, 2026
48af6fc
Resolve reasoning against the effective model
nvdorman Aug 12, 2026
46e11b7
Fix reasoning metadata caching and outages
nvdorman Aug 12, 2026
516a12c
Fix provider cache cancellation and adapter families
nvdorman Aug 12, 2026
e3e2869
Validate reasoning at configuration boundaries
nvdorman Aug 12, 2026
17bd879
Fix reasoning validation write boundaries
nvdorman Aug 12, 2026
0527470
Validate raw candidates with environment overlays
nvdorman Aug 12, 2026
1570731
Adapt reasoning controls to each model
nvdorman Aug 12, 2026
bf9fec4
Preserve reasoning choices across metadata gaps
nvdorman Aug 12, 2026
d5defd4
Debounce model capability lookups
nvdorman Aug 12, 2026
3c86b39
Support resumable rich Cursor streams
nvdorman Aug 12, 2026
f166d74
Pin exact Cursor request wire shape and 410 reset-abort semantics
nvdorman Aug 12, 2026
a9ad3f1
Share Cursor catalogue and run lifecycle
nvdorman Aug 12, 2026
9b4a1e3
Harden Cursor runner failure and payload handling
nvdorman Aug 13, 2026
d3db9bd
Validate Cursor repositories and images
nvdorman Aug 13, 2026
e1bc684
Preserve preflight for unsupported remotes
nvdorman Aug 13, 2026
4169a40
Require explicit approval for Cursor operations
nvdorman Aug 13, 2026
0b00b67
Harden Cursor operation approval boundaries
nvdorman Aug 13, 2026
41b354e
Harden explicit operation outcome reporting
nvdorman Aug 13, 2026
ce9b493
Persist recoverable Cursor run state
nvdorman Aug 13, 2026
f14fa3e
Harden Cursor state persistence safety
nvdorman Aug 13, 2026
d92601f
Run Cursor tools through the shared service
nvdorman Aug 13, 2026
bd92050
Harden Cursor model selection compatibility
nvdorman Aug 13, 2026
9b89bbb
Run Cursor agents directly from chat
nvdorman Aug 13, 2026
b4f96e9
Harden Cursor lifecycle recovery semantics
nvdorman Aug 13, 2026
10daf05
Close Cursor recovery cleanup gaps
nvdorman Aug 13, 2026
1cec696
Close long-run Cursor recovery gaps
nvdorman Aug 13, 2026
0a363e1
Close Cursor recovery correctness edges
nvdorman Aug 13, 2026
cf73409
Add direct Cursor controls to the composer
nvdorman Aug 13, 2026
0de2d92
Hydrate Cursor sessions from durable state
nvdorman Aug 13, 2026
ec61c8e
Keep the composer target true to its session
nvdorman Aug 13, 2026
b4eaff3
Filter Cursor variants instead of stepping one axis
nvdorman Aug 13, 2026
09953f2
Derive session ownership at render time
nvdorman Aug 13, 2026
321485e
Scope composer ownership to route openings
nvdorman Aug 13, 2026
3f7835e
Scope approval and attach work to its route opening
nvdorman Aug 13, 2026
22392df
Merge remote-tracking branch 'origin/main' into feature/cursor-agent-…
nvdorman Aug 13, 2026
cebe2e6
Document adaptive reasoning and direct Cursor mode
nvdorman Aug 13, 2026
2d1607a
Scope the pre-approval guarantee to Cursor mutations
nvdorman Aug 13, 2026
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
1,133 changes: 1,133 additions & 0 deletions .superpowers/sdd/2026-08-12-adaptive-reasoning-cursor-mode/task-12-report.md

Large diffs are not rendered by default.

64 changes: 53 additions & 11 deletions cmd/antares/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
"github.com/enowdev/antares/internal/commands"
"github.com/enowdev/antares/internal/config"
"github.com/enowdev/antares/internal/cron"
"github.com/enowdev/antares/internal/cursor"
"github.com/enowdev/antares/internal/cursorrun"
"github.com/enowdev/antares/internal/gateway"
"github.com/enowdev/antares/internal/httpshim"
"github.com/enowdev/antares/internal/hub"
Expand Down Expand Up @@ -196,16 +198,50 @@ func cmdTUI() error {
// runtimeServices bundles everything a running server needs, so a config reload
// can rebuild the pieces that depend on configuration.
type runtimeServices struct {
mu sync.Mutex
cfg *config.Config
db store.Store
shell *tools.ShellManager
agent *agent.Agent
skills *skills.Manager
cron *cron.Runner
gateway *gateway.Manager
mcp *mcp.Manager
social *socialbrowser.Manager
mu sync.Mutex
cfg *config.Config
db store.Store
shell *tools.ShellManager
agent *agent.Agent
skills *skills.Manager
cron *cron.Runner
gateway *gateway.Manager
mcp *mcp.Manager
social *socialbrowser.Manager
cursorRunner cursorrun.Runner
}

func newRuntimeCursorRunner(ag *agent.Agent) cursorrun.Runner {
return cursorrun.New(cursorrun.Options{
ResolveClient: func() (cursor.Options, error) {
if ag == nil {
return cursor.Options{}, errors.New("Cursor is unavailable in this runtime")
}
cfg := ag.Config()
if cfg == nil {
return cursor.Options{}, errors.New("Cursor is unavailable in this runtime")
}
_, provider := cfg.ResolveProvider("cursor")
provider.APIKey = strings.TrimSpace(provider.APIKey)
options := cursor.Options{
BaseURL: provider.BaseURL,
APIKey: provider.APIKey,
}
if !provider.Enabled || provider.APIKey == "" {
return options, cursorrun.ErrNotConfigured
}
return options, nil
},
Now: time.Now,
CatalogTTL: 5 * time.Minute,
})
}

func (rt *runtimeServices) setCursorRunner(runner cursorrun.Runner) {
rt.cursorRunner = runner
if rt.agent != nil {
rt.agent.SetCursorRunner(runner)
}
}

func bootstrap(ctx context.Context) (*runtimeServices, error) {
Expand Down Expand Up @@ -294,6 +330,7 @@ func bootstrap(ctx context.Context) (*runtimeServices, error) {
ag.SetRoles(roleReg)

rt := &runtimeServices{cfg: cfg, db: db, shell: shell, agent: ag, skills: skillMgr}
rt.setCursorRunner(newRuntimeCursorRunner(ag))
rt.social = socialbrowser.New()
ag.SetSocialBrowser(rt.social)

Expand Down Expand Up @@ -422,14 +459,18 @@ func (rt *runtimeServices) messageIsRelevant(ctx context.Context, b *config.Bind
"\n\nMessage:\n" + strings.TrimSpace(text) +
"\n\nDoes this message fit the criteria and deserve a reply? Answer with exactly one word: YES or NO."

reasoningEffort := ""
if err := rt.agent.ValidateReasoningEffort(ctx, b.Model, "low"); err == nil {
reasoningEffort = "low"
}
var out strings.Builder
_, err := rt.agent.Run(ctx, agent.Request{
Message: prompt,
Model: b.Model, // use the binding's model (or default) for the gate
Toolset: "minimal",
Quiet: true,
MaxTurns: 1,
ReasoningEffort: "low",
ReasoningEffort: reasoningEffort,
}, func(e agent.Event) error {
if e.Type == agent.EventText {
out.WriteString(e.Delta)
Expand Down Expand Up @@ -606,6 +647,7 @@ func cmdServeForeground() error {
Gateway: rt.gateway,
MCP: rt.mcp,
Social: rt.social,
Cursor: rt.cursorRunner,
})

if rt.cfg.Cron.Enabled {
Expand Down
96 changes: 96 additions & 0 deletions cmd/antares/provider_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package main

import (
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"sync"
"sync/atomic"
"testing"

"github.com/enowdev/antares/internal/agent"
"github.com/enowdev/antares/internal/config"
"github.com/enowdev/antares/internal/tools"
)

func TestProviderAddAndUseCursorPreserveActiveModel(t *testing.T) {
Expand Down Expand Up @@ -49,6 +57,94 @@ func TestProviderAddAndUseCursorPreserveActiveModel(t *testing.T) {
}
}

func TestRuntimeCursorRunnerUsesAtomicConfigAndInvalidatesOnReload(t *testing.T) {
var calls atomic.Int32
var version atomic.Int32
version.Store(1)
var authMu sync.Mutex
var authorizations []string
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet || r.URL.Path != "/v1/models" {
t.Errorf("request = %s %s, want GET /v1/models", r.Method, r.URL.Path)
http.NotFound(w, r)
return
}
calls.Add(1)
authMu.Lock()
authorizations = append(authorizations, r.Header.Get("Authorization"))
authMu.Unlock()
_ = json.NewEncoder(w).Encode(map[string]any{
"items": []any{map[string]any{
"id": "model-" + string(rune('0'+version.Load())),
}},
})
}))
defer upstream.Close()

cfg := config.Default()
provider := cfg.Providers["cursor"]
provider.Enabled = true
provider.APIKey = "runtime-key-one"
provider.BaseURL = upstream.URL
cfg.Providers["cursor"] = provider
ag := agent.New(cfg, nil, tools.NewRegistry(), nil, nil)
rt := &runtimeServices{cfg: cfg, agent: ag}
runner := newRuntimeCursorRunner(ag)
rt.setCursorRunner(runner)
if rt.cursorRunner != runner {
t.Fatal("runtimeServices did not retain the installed Cursor runner")
}

first, err := runner.Catalog(context.Background(), false)
if err != nil || len(first.Items) != 1 || first.Items[0].ID != "model-1" {
t.Fatalf("first catalogue = %+v, %v", first, err)
}
if _, err := runner.Catalog(context.Background(), false); err != nil {
t.Fatal(err)
}
if got := calls.Load(); got != 1 {
t.Fatalf("cached catalogue requests = %d, want 1", got)
}

version.Store(2)
reloaded := *cfg
reloaded.Providers = make(map[string]config.Provider, len(cfg.Providers))
for id, configured := range cfg.Providers {
reloaded.Providers[id] = configured
}
ag.SetConfig(&reloaded)
second, err := runner.Catalog(context.Background(), false)
if err != nil || len(second.Items) != 1 || second.Items[0].ID != "model-2" {
t.Fatalf("reloaded catalogue = %+v, %v", second, err)
}
if got := calls.Load(); got != 2 {
t.Fatalf("catalogue requests after same-key reload = %d, want 2", got)
}

changedKey := reloaded
changedKey.Providers = make(map[string]config.Provider, len(reloaded.Providers))
for id, configured := range reloaded.Providers {
changedKey.Providers[id] = configured
}
provider = changedKey.Providers["cursor"]
provider.APIKey = "runtime-key-two"
changedKey.Providers["cursor"] = provider
ag.SetConfig(&changedKey)
if _, err := runner.Catalog(context.Background(), false); err != nil {
t.Fatal(err)
}

authMu.Lock()
gotAuthorizations := append([]string(nil), authorizations...)
authMu.Unlock()
if len(gotAuthorizations) != 3 ||
gotAuthorizations[0] != "Bearer runtime-key-one" ||
gotAuthorizations[1] != "Bearer runtime-key-one" ||
gotAuthorizations[2] != "Bearer runtime-key-two" {
t.Fatalf("resolved authorizations = %v", gotAuthorizations)
}
}

func captureProviderStdout(t *testing.T, f func()) string {
t.Helper()
old := os.Stdout
Expand Down
67 changes: 67 additions & 0 deletions cmd/antares/reasoning_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"context"
"net/http"
"net/http/httptest"
"strings"
"sync/atomic"
"testing"

"github.com/enowdev/antares/internal/agent"
"github.com/enowdev/antares/internal/config"
"github.com/enowdev/antares/internal/store"
"github.com/enowdev/antares/internal/tools"
)

func TestMessageIsRelevantUsesAutoWhenLowUnsupported(t *testing.T) {
var chatCalls atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch {
case r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/models"):
_, _ = w.Write([]byte(`{"data":[{"id":"plain-model","name":"Plain"}]}`))
case r.Method == http.MethodPost && strings.HasSuffix(r.URL.Path, "/chat/completions"):
chatCalls.Add(1)
_, _ = w.Write([]byte(`{"choices":[{"message":{"content":"NO"}}]}`))
default:
http.NotFound(w, r)
}
}))
defer srv.Close()

cfg := config.Default()
cfg.Model.Provider = "router"
cfg.Model.Default = "plain-model"
cfg.Model.MaxRetries = -1
cfg.Model.ReasoningEffort = ""
cfg.Agent.ReasoningEffort = ""
cfg.Streaming.Enabled = false
cfg.Providers = map[string]config.Provider{
"router": {
Kind: "openai-compatible",
BaseURL: srv.URL,
Enabled: true,
},
}
db, err := store.Open(context.Background(), "memory", "", 1, 5000, false)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close() })
rt := &runtimeServices{
cfg: cfg,
db: db,
agent: agent.New(cfg, db, tools.NewRegistry(), nil, nil),
}

if got := rt.messageIsRelevant(context.Background(), &config.Binding{
Model: "plain-model",
RelevanceFilter: "Only answer release announcements.",
}, "How is everyone?"); got {
t.Fatal("messageIsRelevant = true, want classifier's NO response")
}
if got := chatCalls.Load(); got != 1 {
t.Fatalf("classifier chat calls = %d, want one", got)
}
}
1 change: 1 addition & 0 deletions cmd/antares/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func runWebSetup(ctx context.Context, rt *runtimeServices) error {
Config: rt.cfg, Agent: rt.agent, Store: rt.db,
Dist: server.EmbeddedDist(), Reload: rt.reload,
Skills: rt.skills, Cron: rt.cron, Gateway: rt.gateway, MCP: rt.mcp,
Cursor: rt.cursorRunner,
})

urls := setupURLs(port)
Expand Down
Loading
Loading