diff --git a/docs/native_agent_migration.md b/docs/native_agent_migration.md index 316b4f4..772a1d9 100644 --- a/docs/native_agent_migration.md +++ b/docs/native_agent_migration.md @@ -515,7 +515,7 @@ This avoids a broad UI interface change during the first migration. 3. Wire sessionevent SQLite into the native runtime. 4. Add explicit create-or-get session bootstrap. 5. Expose run + session control methods. -6. Wire native `OnEvent` callbacks into a Maestro event adapter for TUI/CLI consumption. +6. Wire the typed `native.Config.EventSink` into a Maestro event adapter for TUI/CLI consumption. 7. Add `bash` environment passthrough configuration for repo-local developer workflows where needed. 8. Define the per-request agent-construction model for concurrency safety. 9. Add `Runtime.Close()` and wire shutdown ownership. @@ -581,7 +581,7 @@ Important wording: 1. Rewire the existing session commands to native/sessionevent-backed storage. 2. Add branch switch/fork commands where missing. 3. Surface current session + branch in the status UI. -4. Stream native lifecycle events through the TUI using the runtime `OnEvent` path and existing `ProgressMsg` plumbing. +4. Stream typed native lifecycle events through the TUI using `native.Config.EventSink` and existing `ProgressMsg` plumbing. #### Acceptance Criteria diff --git a/go.mod b/go.mod index e3ae067..ed1f454 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( charm.land/bubbletea/v2 v2.0.0-rc.2 charm.land/lipgloss/v2 v2.0.0-beta.3.0.20251106193318-19329a3e8410 github.com/AlecAivazis/survey/v2 v2.3.7 - github.com/XiaoConstantine/dspy-go v0.83.1 + github.com/XiaoConstantine/dspy-go v0.86.0 github.com/XiaoConstantine/mcp-go v0.3.1 github.com/briandowns/spinner v1.23.2 github.com/charmbracelet/glamour/v2 v2.0.0-20251106195642-800eb8175930 @@ -16,7 +16,7 @@ require ( github.com/google/uuid v1.6.0 github.com/logrusorgru/aurora v2.0.3+incompatible github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-sqlite3 v1.14.42 + github.com/mattn/go-sqlite3 v1.14.48 github.com/stretchr/testify v1.11.1 golang.org/x/oauth2 v0.34.0 ) @@ -25,11 +25,13 @@ require ( github.com/JohnCGriffin/overflow v0.0.0-20211019200055-46fa312c352c // indirect github.com/alecthomas/chroma/v2 v2.14.0 // indirect github.com/andybalholm/brotli v1.2.0 // indirect - github.com/anthropics/anthropic-sdk-go v1.33.0 // indirect + github.com/anthropics/anthropic-sdk-go v1.38.0 // indirect github.com/apache/arrow/go/v13 v13.0.0 // indirect github.com/apache/thrift v0.23.0 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect github.com/charmbracelet/colorprofile v0.3.3 // indirect github.com/charmbracelet/ultraviolet v0.0.0-20251116181749-377898bcce38 // indirect github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect @@ -44,18 +46,20 @@ require ( github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.2 // indirect + github.com/go-playground/validator/v10 v10.30.3 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/golang/snappy v1.0.0 // indirect github.com/google/flatbuffers v25.9.23+incompatible // indirect github.com/gorilla/css v1.0.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/klauspost/asmfmt v1.3.2 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b // indirect github.com/microcosm-cc/bluemonday v1.0.27 // indirect @@ -71,18 +75,19 @@ require ( github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/traefik/yaegi v0.16.1 // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yuin/goldmark v1.7.17 // indirect github.com/yuin/goldmark-emoji v1.0.5 // indirect github.com/zeebo/xxh3 v1.0.2 // indirect - golang.org/x/crypto v0.52.0 // indirect + golang.org/x/crypto v0.53.0 // indirect golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.55.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect - golang.org/x/text v0.37.0 // indirect - golang.org/x/tools v0.44.0 // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.22.0 // indirect + golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 // indirect + golang.org/x/text v0.40.0 // indirect + golang.org/x/tools v0.47.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect google.golang.org/grpc v1.79.3 // indirect @@ -98,6 +103,6 @@ require ( github.com/spf13/cobra v1.8.1 go.uber.org/atomic v1.7.0 go.uber.org/multierr v1.11.0 // indirect - golang.org/x/sys v0.45.0 // indirect - golang.org/x/term v0.43.0 + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 ) diff --git a/go.sum b/go.sum index 55fe75b..0c69cf7 100644 --- a/go.sum +++ b/go.sum @@ -12,8 +12,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s= github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= -github.com/XiaoConstantine/dspy-go v0.83.1 h1:vYoz1/6tN8lZmdOXytT22C/e7t/xQY1EhY26Yx9u8v4= -github.com/XiaoConstantine/dspy-go v0.83.1/go.mod h1:Uo8dKsqPiS2MWykQoT60lXvpvDvRyu0LzjfKQSPAGeI= +github.com/XiaoConstantine/dspy-go v0.86.0 h1:v/D6BwNTN8b/wema7PuTlBUV3LdpKQlopiGXBC4mrsQ= +github.com/XiaoConstantine/dspy-go v0.86.0/go.mod h1:GNqqozCBAU5vatQGrNKpehBz4W0ZOu1YOiiNMAtm5wU= github.com/XiaoConstantine/mcp-go v0.3.1 h1:5aSrqvvulbKI1hxN7Aj3iSotX0VRSpDShXIDls4ZC+U= github.com/XiaoConstantine/mcp-go v0.3.1/go.mod h1:qsY3Gm4CYMREmoLXEMwtf0Hdb5F957kgcpF6huoNHcY= github.com/alecthomas/assert/v2 v2.7.0 h1:QtqSACNS3tF7oasA8CU6A6sXZSBDqnm7RfpLl9bZqbE= @@ -24,8 +24,8 @@ github.com/alecthomas/repr v0.4.0 h1:GhI2A8MACjfegCPVq9f1FLvIBS+DrQ2KQBFZP1iFzXc github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4= github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/anthropics/anthropic-sdk-go v1.33.0 h1:YlRqiI+PjULBA8NoeeJmkXtFzTmjZZA7oFvpZ4FU7eU= -github.com/anthropics/anthropic-sdk-go v1.33.0/go.mod h1:dSIO7kSrOI7MA4fE6RRVaw8tyWP7HNQU5/H/KS4cax8= +github.com/anthropics/anthropic-sdk-go v1.38.0 h1:bA4DcK+91gorIX+5VTONnynyt9LRU4nnN6rRQ+j/NIg= +github.com/anthropics/anthropic-sdk-go v1.38.0/go.mod h1:d288C1L+m74OYuYBvc4UFtR1Q8J0gC55oYDh2t+XxdI= github.com/apache/arrow/go/v13 v13.0.0 h1:kELrvDQuKZo8csdWYqBQfyi431x6Zs/YJTEgUuSVcWk= github.com/apache/arrow/go/v13 v13.0.0/go.mod h1:W69eByFNO0ZR30q1/7Sr9d83zcVZmF2MiP3fFYAWJOc= github.com/apache/thrift v0.23.0 h1:wKR6YnefQSEnxpEfmgTPuJibNG4bF0p2TK34tHLWi3s= @@ -36,8 +36,12 @@ github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3v github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/briandowns/spinner v1.23.2 h1:Zc6ecUnI+YzLmJniCfDNaMbW0Wid1d5+qcTq4L2FW8w= github.com/briandowns/spinner v1.23.2/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/charmbracelet/colorprofile v0.3.3 h1:DjJzJtLP6/NZ8p7Cgjno0CKGr7wwRJGxWUwh2IyhfAI= @@ -88,8 +92,8 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.2 h1:JiFIMtSSHb2/XBUbWM4i/MpeQm9ZK2xqPNk8vgvu5JQ= -github.com/go-playground/validator/v10 v10.30.2/go.mod h1:mAf2pIOVXjTEBrwUMGKkCWKKPs9NheYGabeB04txQSc= +github.com/go-playground/validator/v10 v10.30.3 h1:4MU6YkEwx7GbcPJOZxrtbu+QfF3pJLJuaYTeAH0DYy8= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= @@ -115,6 +119,9 @@ github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= github.com/klauspost/asmfmt v1.3.2 h1:4Ri7ox3EwapiOjCki+hw14RyKk201CN4rzyCJRFLpK4= @@ -133,6 +140,8 @@ github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczG github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= @@ -145,8 +154,8 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= -github.com/mattn/go-sqlite3 v1.14.42 h1:MigqEP4ZmHw3aIdIT7T+9TLa90Z6smwcthx+Azv4Cgo= -github.com/mattn/go-sqlite3 v1.14.42/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ= +github.com/mattn/go-sqlite3 v1.14.48 h1:7XHIgl0a8HwOaiK4E47ozLkST78rR9+OtNGx27D/TFs= +github.com/mattn/go-sqlite3 v1.14.48/go.mod h1:6JTjA44L93a0QCyJef5YvlPoKXntQPjzWv5gtm9sB6w= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= @@ -192,6 +201,8 @@ github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/traefik/yaegi v0.16.1 h1:f1De3DVJqIDKmnasUF6MwmWv1dSEEat0wcpXhD2On3E= github.com/traefik/yaegi v0.16.1/go.mod h1:4eVhbPb3LnD2VigQjhYbEJ69vDRFdT2HQNrXx8eEwUY= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= @@ -224,24 +235,24 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= -golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b h1:18qgiDvlvH7kk8Ioa8Ov+K6xCi0GMvmGfGW0sgd/SYA= golang.org/x/exp v0.0.0-20251009144603-d2f985daa21b/go.mod h1:j/pmGrbnkbPtQfxEe5D0VQhZC6qKbfKifgD0oM7sR70= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= -golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -253,25 +264,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY= -golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= -golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57 h1:nwGZBCt+FnXUrGsj5vjzAsEmkcaFvd82BbOjECiFYZc= +golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= -golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= -golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY= diff --git a/internal/orchestration/native_qa.go b/internal/orchestration/native_qa.go index 106a353..5e80e92 100644 --- a/internal/orchestration/native_qa.go +++ b/internal/orchestration/native_qa.go @@ -11,7 +11,7 @@ import ( "strconv" "strings" - "github.com/XiaoConstantine/dspy-go/pkg/agents/native" + "github.com/XiaoConstantine/dspy-go/pkg/agents" "github.com/XiaoConstantine/dspy-go/pkg/agents/sessionevent" "github.com/XiaoConstantine/dspy-go/pkg/core" "github.com/XiaoConstantine/dspy-go/pkg/logging" @@ -380,7 +380,7 @@ func newNativeToolResult(modelText, displayText string, details map[string]any) } } -func extractSourcesFromNativeTrace(trace *native.Trace) []string { +func extractSourcesFromExecutionTrace(trace *agents.ExecutionTrace) []string { if trace == nil { return nil } @@ -397,8 +397,8 @@ func extractSourcesFromNativeTrace(trace *native.Trace) []string { } for _, step := range trace.Steps { - addTraceSources(add, step.ToolName, step.ObservationDetails) - if strings.EqualFold(step.ToolName, "read_file") { + addTraceSources(add, step.Tool, step.ObservationDetails) + if strings.EqualFold(step.Tool, "read_file") { add(firstString(step.Arguments, "file_path", "path", "filepath", "file")) } } @@ -462,8 +462,8 @@ func addFilesFromResults(add func(string), value any) { } } -func estimateNativeQAConfidence(trace *native.Trace, sources []string) float64 { - if trace == nil || !trace.Completed { +func estimateNativeQAConfidence(trace *agents.ExecutionTrace, sources []string) float64 { + if trace == nil || trace.Status != agents.TraceStatusSuccess { return 0 } @@ -472,7 +472,7 @@ func estimateNativeQAConfidence(trace *native.Trace, sources []string) float64 { confidence = 0.9 } for _, step := range trace.Steps { - if step.IsError { + if step.Error != "" { confidence -= 0.2 } } diff --git a/internal/orchestration/native_qa_test.go b/internal/orchestration/native_qa_test.go index c09bf91..0e66103 100644 --- a/internal/orchestration/native_qa_test.go +++ b/internal/orchestration/native_qa_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - "github.com/XiaoConstantine/dspy-go/pkg/agents/native" + "github.com/XiaoConstantine/dspy-go/pkg/agents" "github.com/XiaoConstantine/dspy-go/pkg/core" "github.com/XiaoConstantine/dspy-go/pkg/logging" ) @@ -44,18 +44,18 @@ func TestBuildNativeSearchToolsSchemas(t *testing.T) { } } -func TestExtractSourcesFromNativeTrace(t *testing.T) { - trace := &native.Trace{ - Completed: true, - Steps: []native.TraceStep{ +func TestExtractSourcesFromExecutionTrace(t *testing.T) { + trace := &agents.ExecutionTrace{ + Status: agents.TraceStatusSuccess, + Steps: []agents.TraceStep{ { - ToolName: "search_files", + Tool: "search_files", ObservationDetails: map[string]any{ "files": []string{"README.md", "internal/orchestration/pool.go"}, }, }, { - ToolName: "search_content", + Tool: "search_content", ObservationDetails: map[string]any{ "results": []map[string]any{ {"file_path": "internal/orchestration/pool.go", "line_number": 12}, @@ -64,7 +64,7 @@ func TestExtractSourcesFromNativeTrace(t *testing.T) { }, }, { - ToolName: "read_file", + Tool: "read_file", Arguments: map[string]any{ "path": "internal/orchestration/native_qa.go", }, @@ -72,14 +72,45 @@ func TestExtractSourcesFromNativeTrace(t *testing.T) { }, } - got := extractSourcesFromNativeTrace(trace) + got := extractSourcesFromExecutionTrace(trace) want := []string{ "README.md", "internal/orchestration/pool.go", "internal/orchestration/native_qa.go", } if !reflect.DeepEqual(got, want) { - t.Fatalf("extractSourcesFromNativeTrace() = %#v, want %#v", got, want) + t.Fatalf("extractSourcesFromExecutionTrace() = %#v, want %#v", got, want) + } +} + +func TestEstimateNativeQAConfidenceFromExecutionTrace(t *testing.T) { + tests := []struct { + name string + trace *agents.ExecutionTrace + sources []string + want float64 + }{ + {name: "nil trace", want: 0}, + {name: "partial run", trace: &agents.ExecutionTrace{Status: agents.TraceStatusPartial}, want: 0}, + {name: "successful run", trace: &agents.ExecutionTrace{Status: agents.TraceStatusSuccess}, want: 0.7}, + {name: "successful run with sources", trace: &agents.ExecutionTrace{Status: agents.TraceStatusSuccess}, sources: []string{"README.md"}, want: 0.9}, + { + name: "tool error lowers confidence", + trace: &agents.ExecutionTrace{ + Status: agents.TraceStatusSuccess, + Steps: []agents.TraceStep{{Tool: "read_file", Error: "not found"}}, + }, + sources: []string{"README.md"}, + want: 0.7, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := estimateNativeQAConfidence(tt.trace, tt.sources); got != tt.want { + t.Fatalf("estimateNativeQAConfidence() = %v, want %v", got, tt.want) + } + }) } } diff --git a/internal/orchestration/pool.go b/internal/orchestration/pool.go index f1363e9..df0b47b 100644 --- a/internal/orchestration/pool.go +++ b/internal/orchestration/pool.go @@ -146,35 +146,40 @@ func (a *QAAgent) ConfigureSession(sessionManager *maestrosubagent.SessionManage } func (a *QAAgent) Ask(ctx context.Context, question, repoPath, owner, repo string) (string, float64, []string, error) { + answer, confidence, sources, _, err := a.askWithTrace(ctx, question, repoPath, owner, repo) + return answer, confidence, sources, err +} + +func (a *QAAgent) askWithTrace(ctx context.Context, question, repoPath, owner, repo string) (string, float64, []string, *agents.ExecutionTrace, error) { a.mu.Lock() defer a.mu.Unlock() a.logger.Debug(ctx, "QAAgent Ask start: session=%q repo=%q question=%q", a.sessionID, repoPath, question) if err := a.ensureNativeAgentLocked(ctx, repoPath, owner, repo); err != nil { a.logger.Debug(ctx, "QAAgent ensureNativeAgentLocked error: %v", err) - return "", 0, nil, fmt.Errorf("failed to create native QA agent: %w", err) + return "", 0, nil, nil, fmt.Errorf("failed to create native QA agent: %w", err) } - answer, confidence, sources, err := a.askWithNativeLocked(ctx, question, owner, repo) + answer, confidence, sources, trace, err := a.askWithNativeLocked(ctx, question, owner, repo) if err == nil { - trace := a.nativeAgent.LastNativeTrace() var promptTokens, completionTokens, totalTokens int64 var steps int if trace != nil { - promptTokens = trace.TokenUsage.PromptTokens - completionTokens = trace.TokenUsage.CompletionTokens - totalTokens = trace.TokenUsage.TotalTokens + promptTokens = trace.TokenUsage["prompt_tokens"] + completionTokens = trace.TokenUsage["completion_tokens"] + totalTokens = trace.TokenUsage["total_tokens"] steps = len(trace.Steps) } a.logger.Debug(ctx, "QAAgent native ask complete: answer_len=%d confidence=%.2f sources=%d prompt_tokens=%d completion_tokens=%d total_tokens=%d steps=%d", len(answer), confidence, len(sources), promptTokens, completionTokens, totalTokens, steps) - return answer, confidence, sources, nil + return answer, confidence, sources, trace, nil } a.logger.Debug(ctx, "QAAgent native ask error: %v", err) if shouldFallbackToLegacyQA(err) { a.logger.Warn(ctx, "Native QA failed, falling back to legacy ReAct: %v", err) - return a.askWithLegacyReAct(ctx, question, repoPath, owner, repo) + answer, confidence, sources, fallbackErr := a.askWithLegacyReAct(ctx, question, repoPath, owner, repo) + return answer, confidence, sources, trace, fallbackErr } - return "", 0, nil, err + return "", 0, nil, trace, err } func (a *QAAgent) SkillState() (string, int) { @@ -183,34 +188,33 @@ func (a *QAAgent) SkillState() (string, int) { return a.skillDomain, a.loadedSkillVersion } -func (a *QAAgent) askWithNativeLocked(ctx context.Context, question, owner, repo string) (string, float64, []string, error) { - result, err := a.nativeAgent.Execute(ctx, map[string]interface{}{ +func (a *QAAgent) askWithNativeLocked(ctx context.Context, question, owner, repo string) (string, float64, []string, *agents.ExecutionTrace, error) { + execution, err := a.nativeAgent.ExecuteWithTrace(ctx, map[string]interface{}{ "task": buildNativeQATask(question, owner, repo), }) + trace := execution.Trace if err != nil { - return "", 0, nil, err + return "", 0, nil, trace, err } + result := execution.Output answer := strings.TrimSpace(stringValue(result["final_answer"])) - if answer == "" { - if trace := a.nativeAgent.LastNativeTrace(); trace != nil { - answer = strings.TrimSpace(trace.FinalAnswer) - } + if answer == "" && trace != nil { + answer = strings.TrimSpace(stringValue(trace.Output["final_answer"])) } if answer == "" { if execErr := strings.TrimSpace(stringValue(result["error"])); execErr != "" { - return "", 0, nil, fmt.Errorf("%s", execErr) + return "", 0, nil, trace, fmt.Errorf("%s", execErr) } } if answer == "" { answer = fmt.Sprintf("I couldn't find relevant information about \"%s\" in this repository.", question) } - trace := a.nativeAgent.LastNativeTrace() - sources := extractSourcesFromNativeTrace(trace) + sources := extractSourcesFromExecutionTrace(trace) confidence := estimateNativeQAConfidence(trace, sources) - return answer, confidence, sources, nil + return answer, confidence, sources, trace, nil } func (a *QAAgent) askWithLegacyReAct(ctx context.Context, question, repoPath, owner, repo string) (string, float64, []string, error) { diff --git a/internal/orchestration/qa_benchmark.go b/internal/orchestration/qa_benchmark.go index 46adc45..87d2306 100644 --- a/internal/orchestration/qa_benchmark.go +++ b/internal/orchestration/qa_benchmark.go @@ -44,9 +44,8 @@ type QABenchmarkAgent struct { logger *logging.Logger artifacts optimize.AgentArtifacts - mu sync.RWMutex - lastTrace *agents.ExecutionTrace - lastNativeTrace *native.Trace + mu sync.RWMutex + lastTrace *agents.ExecutionTrace } var _ optimize.OptimizableAgent = (*QABenchmarkAgent)(nil) @@ -275,7 +274,6 @@ func (a *QABenchmarkAgent) Execute(ctx context.Context, input map[string]interfa a.mu.Lock() a.lastTrace = runtimeAgent.LastExecutionTrace() - a.lastNativeTrace = runtimeAgent.LastNativeTrace() a.mu.Unlock() return result, err @@ -348,9 +346,6 @@ func (a *QABenchmarkAgent) Clone() (optimize.OptimizableAgent, error) { if trace := a.LastExecutionTrace(); trace != nil { cloned.lastTrace = trace } - if trace := a.LastNativeTrace(); trace != nil { - cloned.lastNativeTrace = trace - } return cloned, nil } @@ -366,18 +361,6 @@ func (a *QABenchmarkAgent) LastExecutionTrace() *agents.ExecutionTrace { return a.lastTrace.Clone() } -func (a *QABenchmarkAgent) LastNativeTrace() *native.Trace { - if a == nil { - return nil - } - a.mu.RLock() - defer a.mu.RUnlock() - if a.lastNativeTrace == nil { - return nil - } - return a.lastNativeTrace.Clone() -} - func (e *qaBenchmarkEvaluator) Evaluate(ctx context.Context, agent optimize.OptimizableAgent, ex optimize.AgentExample) (*optimize.EvalResult, error) { benchmarkCase, err := qaBenchmarkCaseFromExample(ex) if err != nil { @@ -440,19 +423,18 @@ func (e *qaBenchmarkEvaluator) Evaluate(ctx context.Context, agent optimize.Opti sideInfo.Trace = trace if trace != nil { sideInfo.Tokens = traceTokenUsage(trace) - } - } - if nativeTraceProvider, ok := agent.(interface{ LastNativeTrace() *native.Trace }); ok { - if trace := nativeTraceProvider.LastNativeTrace(); trace != nil { - sideInfo.Diagnostics["native_prompt_tokens"] = trace.TokenUsage.PromptTokens - sideInfo.Diagnostics["native_completion_tokens"] = trace.TokenUsage.CompletionTokens - sideInfo.Diagnostics["native_total_tokens"] = trace.TokenUsage.TotalTokens + promptTokens := trace.TokenUsage["prompt_tokens"] + completionTokens := trace.TokenUsage["completion_tokens"] + totalTokens := trace.TokenUsage["total_tokens"] + sideInfo.Diagnostics["native_prompt_tokens"] = promptTokens + sideInfo.Diagnostics["native_completion_tokens"] = completionTokens + sideInfo.Diagnostics["native_total_tokens"] = totalTokens if sideInfo.Tokens == nil { sideInfo.Tokens = map[string]int64{} } - sideInfo.Tokens["native_prompt_tokens"] = trace.TokenUsage.PromptTokens - sideInfo.Tokens["native_completion_tokens"] = trace.TokenUsage.CompletionTokens - sideInfo.Tokens["native_total_tokens"] = trace.TokenUsage.TotalTokens + sideInfo.Tokens["native_prompt_tokens"] = promptTokens + sideInfo.Tokens["native_completion_tokens"] = completionTokens + sideInfo.Tokens["native_total_tokens"] = totalTokens } } diff --git a/internal/orchestration/qa_benchmark_test.go b/internal/orchestration/qa_benchmark_test.go index 47a477b..620247f 100644 --- a/internal/orchestration/qa_benchmark_test.go +++ b/internal/orchestration/qa_benchmark_test.go @@ -17,9 +17,8 @@ import ( ) type fakeQABenchmarkAgent struct { - answer string - trace *agents.ExecutionTrace - nativeTrace *native.Trace + answer string + trace *agents.ExecutionTrace } type capturingBenchmarkLLM struct { @@ -90,7 +89,7 @@ func (a *fakeQABenchmarkAgent) GetArtifacts() optimize.AgentArtifacts { func (a *fakeQABenchmarkAgent) SetArtifacts(optimize.AgentArtifacts) error { return nil } func (a *fakeQABenchmarkAgent) Clone() (optimize.OptimizableAgent, error) { - return &fakeQABenchmarkAgent{answer: a.answer, trace: a.trace, nativeTrace: a.nativeTrace}, nil + return &fakeQABenchmarkAgent{answer: a.answer, trace: a.trace}, nil } func (a *fakeQABenchmarkAgent) LastExecutionTrace() *agents.ExecutionTrace { @@ -100,27 +99,15 @@ func (a *fakeQABenchmarkAgent) LastExecutionTrace() *agents.ExecutionTrace { return a.trace.Clone() } -func (a *fakeQABenchmarkAgent) LastNativeTrace() *native.Trace { - if a.nativeTrace == nil { - return nil - } - return a.nativeTrace.Clone() -} - func TestQABenchmarkEvaluatorScoresFactCoverage(t *testing.T) { evaluator := NewQABenchmarkEvaluator(DefaultQABenchmarkEvaluatorConfig()) agent := &fakeQABenchmarkAgent{ answer: "The repository is organized around pkg/agents and pkg/modules. pkg/agents contains runtime agents.", trace: &agents.ExecutionTrace{ TokenUsage: map[string]int64{ - "total_tokens": 1234, - }, - }, - nativeTrace: &native.Trace{ - TokenUsage: native.TokenUsage{ - PromptTokens: 700, - CompletionTokens: 300, - TotalTokens: 1000, + "prompt_tokens": 700, + "completion_tokens": 300, + "total_tokens": 1000, }, }, } @@ -141,8 +128,8 @@ func TestQABenchmarkEvaluatorScoresFactCoverage(t *testing.T) { if result.Score != 1.0 { t.Fatalf("score = %v, want 1.0", result.Score) } - if result.SideInfo == nil || result.SideInfo.Tokens["total_tokens"] != 1234 { - t.Fatalf("tokens = %#v, want total_tokens=1234", result.SideInfo) + if result.SideInfo == nil || result.SideInfo.Tokens["total_tokens"] != 1000 { + t.Fatalf("tokens = %#v, want total_tokens=1000", result.SideInfo) } if result.SideInfo.Tokens["native_total_tokens"] != 1000 { t.Fatalf("native_total_tokens = %#v, want 1000", result.SideInfo.Tokens) @@ -369,6 +356,57 @@ func TestQABenchmarkAgent_Execute_ComposesBaseAndOverlayOnce(t *testing.T) { } } +func TestQAAgentNativeExecutionReturnsOperationScopedTrace(t *testing.T) { + llm := &capturingBenchmarkLLM{ + capabilities: []core.Capability{core.CapabilityCompletion, core.CapabilityToolCalling}, + results: []map[string]any{ + {"function_call": map[string]any{"name": "Finish", "arguments": map[string]any{"answer": "first"}}}, + {"function_call": map[string]any{"name": "Finish", "arguments": map[string]any{"answer": "second"}}}, + }, + } + runtimeAgent, err := native.NewAgent(llm, native.Config{MaxTurns: 1}) + if err != nil { + t.Fatalf("native.NewAgent() error = %v", err) + } + agent := &QAAgent{nativeAgent: runtimeAgent} + + firstAnswer, _, _, firstTrace, err := agent.askWithNativeLocked(context.Background(), "first question", "owner", "repo") + if err != nil { + t.Fatalf("first askWithNativeLocked() error = %v", err) + } + secondAnswer, _, _, secondTrace, err := agent.askWithNativeLocked(context.Background(), "second question", "owner", "repo") + if err != nil { + t.Fatalf("second askWithNativeLocked() error = %v", err) + } + + if firstAnswer != "first" || secondAnswer != "second" { + t.Fatalf("answers = %q, %q; want first, second", firstAnswer, secondAnswer) + } + if firstTrace == nil || secondTrace == nil { + t.Fatalf("traces = %#v, %#v; want both non-nil", firstTrace, secondTrace) + } + if got := stringValue(firstTrace.Output["final_answer"]); got != "first" { + t.Fatalf("first trace final answer after second run = %q, want first", got) + } + if firstTrace == secondTrace { + t.Fatal("first and second execution returned the same trace pointer") + } + + _, _, _, failedTrace, err := agent.askWithNativeLocked(context.Background(), "failing question", "owner", "repo") + if err == nil { + t.Fatal("failing askWithNativeLocked() error = nil") + } + if failedTrace == nil { + t.Fatal("failed trace = nil") + } + if failedTrace == secondTrace { + t.Fatal("failed execution reused the prior trace pointer") + } + if failedTrace.Status != agents.TraceStatusFailure { + t.Fatalf("failed trace status = %q, want %q", failedTrace.Status, agents.TraceStatusFailure) + } +} + func TestQASkillStorePublishPath_LoadsOverlayWithoutDuplication(t *testing.T) { store := skills.NewFileStore(filepath.Join(t.TempDir(), "skills.json")) overlay := "Prefer exact symbol lookups before broad searches." @@ -431,6 +469,16 @@ func TestQASkillStorePublishPath_LoadsOverlayWithoutDuplication(t *testing.T) { if completed, _ := result["completed"].(bool); !completed { t.Fatalf("completed = %#v, want true", result["completed"]) } + trace := runtimeAgent.LastExecutionTrace() + if trace == nil { + t.Fatal("LastExecutionTrace() = nil") + } + if trace.Status != agents.TraceStatusSuccess { + t.Fatalf("trace status = %q, want %q", trace.Status, agents.TraceStatusSuccess) + } + if answer := stringValue(trace.Output["final_answer"]); answer != "done" { + t.Fatalf("trace final answer = %q, want done", answer) + } if len(llm.prompts) != 1 { t.Fatalf("captured prompts = %d, want 1", len(llm.prompts)) } diff --git a/internal/orchestration/rlm_budget.go b/internal/orchestration/rlm_budget.go index a7934da..fa802a7 100644 --- a/internal/orchestration/rlm_budget.go +++ b/internal/orchestration/rlm_budget.go @@ -3,10 +3,24 @@ package orchestration import ( "context" + "github.com/XiaoConstantine/dspy-go/pkg/agents" modrlm "github.com/XiaoConstantine/dspy-go/pkg/modules/rlm" maestrobudget "github.com/XiaoConstantine/maestro/internal/budget" ) +func (s *MaestroService) recordExecutionTraceUsage(ctx context.Context, agent string, trace *agents.ExecutionTrace) { + if s == nil || s.budgetManager == nil || trace == nil { + return + } + delta := maestrobudget.UsageDeltaFromExecutionTrace(trace) + if delta.Empty() { + return + } + if err := s.budgetManager.RecordUsageDelta(agent, delta); err != nil && s.logger != nil { + s.logger.Warn(ctx, "Failed to record execution-trace budget usage for %s: %v", agent, err) + } +} + func (s *MaestroService) recordRLMTraceUsage(ctx context.Context, agent string, trace *modrlm.RLMTrace) { if s == nil || s.budgetManager == nil || trace == nil { return diff --git a/internal/orchestration/rlm_budget_test.go b/internal/orchestration/rlm_budget_test.go index 6a9fbb9..e1ee350 100644 --- a/internal/orchestration/rlm_budget_test.go +++ b/internal/orchestration/rlm_budget_test.go @@ -5,6 +5,7 @@ import ( "testing" "time" + "github.com/XiaoConstantine/dspy-go/pkg/agents" "github.com/XiaoConstantine/dspy-go/pkg/core" "github.com/XiaoConstantine/dspy-go/pkg/logging" modrlm "github.com/XiaoConstantine/dspy-go/pkg/modules/rlm" @@ -12,6 +13,32 @@ import ( "github.com/XiaoConstantine/maestro/internal/types" ) +func TestRecordExecutionTraceUsageUpdatesBudgetStatus(t *testing.T) { + manager := maestrobudget.NewBudgetManager(maestrobudget.DefaultConfig()) + service := &MaestroService{ + budgetManager: manager, + logger: logging.GetLogger(), + } + service.recordExecutionTraceUsage(context.Background(), "ask.native", &agents.ExecutionTrace{ + TokenUsage: map[string]int64{ + "prompt_tokens": 100, + "completion_tokens": 25, + "total_tokens": 125, + }, + }) + + status := service.BudgetStatus() + if status == nil { + t.Fatal("BudgetStatus() = nil") + } + if status.TotalTokens != 125 { + t.Fatalf("TotalTokens = %d, want 125", status.TotalTokens) + } + if got := status.ByAgent["ask.native"].TotalTokens; got != 125 { + t.Fatalf("agent tokens = %d, want 125", got) + } +} + func TestRecordRLMTraceUsageUpdatesBudgetStatus(t *testing.T) { manager := maestrobudget.NewBudgetManager(maestrobudget.Config{MaxBudgetUSD: 1.00}) service := &MaestroService{ diff --git a/internal/orchestration/service.go b/internal/orchestration/service.go index 8d842bf..0e935ab 100644 --- a/internal/orchestration/service.go +++ b/internal/orchestration/service.go @@ -369,7 +369,8 @@ func (s *MaestroService) handleAsk(ctx context.Context, request Request) (*Respo return nil, fmt.Errorf("failed to get QA agent: %w", err) } - answer, confidence, sources, err := agent.Ask(ctx, request.Question, repoPath, s.config.Owner, s.config.Repo) + answer, confidence, sources, trace, err := agent.askWithTrace(ctx, request.Question, repoPath, s.config.Owner, s.config.Repo) + s.recordExecutionTraceUsage(ctx, "ask.native", trace) if err != nil { return nil, err } diff --git a/internal/review/verifier.go b/internal/review/verifier.go index 1cbf814..7a18392 100644 --- a/internal/review/verifier.go +++ b/internal/review/verifier.go @@ -213,8 +213,8 @@ func (a *PRReviewAgent) verifyReviewComments(ctx context.Context, comments []PRR raw := strings.TrimSpace(reviewStringValue(result["final_answer"])) if raw == "" { - if trace := verifier.LastNativeTrace(); trace != nil { - raw = strings.TrimSpace(trace.FinalAnswer) + if trace := verifier.LastExecutionTrace(); trace != nil { + raw = strings.TrimSpace(reviewStringValue(trace.Output["final_answer"])) } } if raw == "" {