Skip to content

Commit a0d91c4

Browse files
committed
feat(team): integrate refined team tool and align with upstream
- Merge feat/team: integrate multi-agent orchestration tool into main. - Fix Team Tool bugs: resolve DAG goroutine leaks and token budget clamping. - Optimize Team strategies: implement text-only evaluator and partial parallel success. - Enhance observability: add structured logging and improved user summaries. - Expand configuration: add max_context_runes and reviewer_model settings. - Refactor context builder: implement cached system prompt and simplified dynamic context. - Align with upstream: remove local Embedding support and custom truncation logic for better parity. - Documentation: update tools_configuration.md with detailed Team tool guide.
1 parent e40752a commit a0d91c4

6 files changed

Lines changed: 6 additions & 123 deletions

File tree

pkg/agent/context.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ The following skills extend your capabilities. To use a skill, read its SKILL.md
153153
}
154154

155155
// Memory context
156-
memoryContext := cb.memory.GetMemoryContext("")
156+
memoryContext := cb.memory.GetMemoryContext()
157157
if memoryContext != "" {
158158
parts = append(parts, "# Memory\n\n"+memoryContext)
159159
}

pkg/agent/memory.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ import (
2020
// - Long-term memory: memory/MEMORY.md
2121
// - Daily notes: memory/YYYYMM/YYYYMMDD.md
2222
type MemoryStore struct {
23-
workspace string
24-
memoryDir string
25-
memoryFile string
26-
lastSyncTime time.Time
23+
workspace string
24+
memoryDir string
25+
memoryFile string
2726
}
2827

2928
// NewMemoryStore creates a new MemoryStore with the given workspace path.
@@ -132,7 +131,7 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string {
132131

133132
// GetMemoryContext returns formatted memory context for the agent prompt.
134133
// It loads the full MEMORY.md.
135-
func (ms *MemoryStore) GetMemoryContext(query string) string {
134+
func (ms *MemoryStore) GetMemoryContext() string {
136135
longTerm := ms.ReadLongTerm()
137136

138137
recentNotes := ms.GetRecentDailyNotes(3)

pkg/providers/fallback.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func ResolveCandidatesWithLookup(
5656

5757
addCandidate := func(raw string) {
5858
candidateRaw := strings.TrimSpace(raw)
59-
6059
if lookup != nil {
6160
if resolved, ok := lookup(candidateRaw); ok {
6261
candidateRaw = resolved

pkg/providers/openai_compat/provider.go

Lines changed: 1 addition & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -272,58 +272,6 @@ func responsePreview(body []byte, maxLen int) string {
272272
return string(trimmed[:maxLen]) + "..."
273273
}
274274

275-
// Embed implements providers.EmbedProvider by calling /v1/embeddings.
276-
// The Provider satisfies EmbedProvider optionally — callers should type-assert.
277-
func (p *Provider) Embed(ctx context.Context, text string, model string) ([]float32, error) {
278-
if p.apiBase == "" {
279-
return nil, fmt.Errorf("API base not configured")
280-
}
281-
282-
reqBody, err := json.Marshal(map[string]any{
283-
"model": model,
284-
"input": text,
285-
})
286-
if err != nil {
287-
return nil, fmt.Errorf("failed to marshal embed request: %w", err)
288-
}
289-
290-
req, err := http.NewRequestWithContext(ctx, "POST", p.apiBase+"/embeddings", bytes.NewReader(reqBody))
291-
if err != nil {
292-
return nil, fmt.Errorf("failed to create embed request: %w", err)
293-
}
294-
req.Header.Set("Content-Type", "application/json")
295-
if p.apiKey != "" {
296-
req.Header.Set("Authorization", "Bearer "+p.apiKey)
297-
}
298-
299-
resp, err := p.httpClient.Do(req)
300-
if err != nil {
301-
return nil, fmt.Errorf("embed request failed: %w", err)
302-
}
303-
defer resp.Body.Close()
304-
305-
body, err := io.ReadAll(resp.Body)
306-
if err != nil {
307-
return nil, fmt.Errorf("failed to read embed response: %w", err)
308-
}
309-
if resp.StatusCode != http.StatusOK {
310-
return nil, fmt.Errorf("embed API error: status %d: %s", resp.StatusCode, body)
311-
}
312-
313-
var result struct {
314-
Data []struct {
315-
Embedding []float32 `json:"embedding"`
316-
} `json:"data"`
317-
}
318-
if err := json.Unmarshal(body, &result); err != nil {
319-
return nil, fmt.Errorf("failed to decode embed response: %w", err)
320-
}
321-
if len(result.Data) == 0 || len(result.Data[0].Embedding) == 0 {
322-
return nil, fmt.Errorf("embed response contained no data")
323-
}
324-
return result.Data[0].Embedding, nil
325-
}
326-
327275
func parseResponse(body io.Reader) (*LLMResponse, error) {
328276
var apiResponse struct {
329277
Choices []struct {
@@ -364,7 +312,6 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
364312

365313
choice := apiResponse.Choices[0]
366314
toolCalls := make([]ToolCall, 0, len(choice.Message.ToolCalls))
367-
truncated := false
368315
for _, tc := range choice.Message.ToolCalls {
369316
arguments := make(map[string]any)
370317
name := ""
@@ -399,19 +346,13 @@ func parseResponse(body io.Reader) (*LLMResponse, error) {
399346
toolCalls = append(toolCalls, toolCall)
400347
}
401348

402-
finishReason := choice.FinishReason
403-
// Propagate truncation: if finish_reason is "length" or we detected bad JSON, mark as truncated.
404-
if truncated || finishReason == "length" {
405-
finishReason = "truncated"
406-
}
407-
408349
return &LLMResponse{
409350
Content: choice.Message.Content,
410351
ReasoningContent: choice.Message.ReasoningContent,
411352
Reasoning: choice.Message.Reasoning,
412353
ReasoningDetails: choice.Message.ReasoningDetails,
413354
ToolCalls: toolCalls,
414-
FinishReason: finishReason,
355+
FinishReason: choice.FinishReason,
415356
Usage: apiResponse.Usage,
416357
}, nil
417358
}

pkg/providers/openai_compat/provider_test.go

Lines changed: 0 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -841,51 +841,3 @@ func TestSerializeMessages_StripsSystemParts(t *testing.T) {
841841
t.Fatal("system_parts should not appear in serialized output")
842842
}
843843
}
844-
845-
func TestProviderChat_RepairsTruncatedToolCall(t *testing.T) {
846-
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
847-
resp := map[string]any{
848-
"choices": []map[string]any{
849-
{
850-
"message": map[string]any{
851-
"content": "",
852-
"tool_calls": []map[string]any{
853-
{
854-
"id": "call_1",
855-
"type": "function",
856-
"function": map[string]any{
857-
"name": "read_file",
858-
"arguments": "{\"path\": \"/my/file.txt\"", // missing }
859-
},
860-
},
861-
},
862-
},
863-
"finish_reason": "length",
864-
},
865-
},
866-
}
867-
w.Header().Set("Content-Type", "application/json")
868-
json.NewEncoder(w).Encode(resp)
869-
}))
870-
defer server.Close()
871-
872-
p := NewProvider("key", server.URL, "")
873-
out, err := p.Chat(t.Context(), []Message{{Role: "user", Content: "hi"}}, nil, "gpt-4o", nil)
874-
if err != nil {
875-
t.Fatalf("Chat() error = %v", err)
876-
}
877-
if len(out.ToolCalls) != 1 {
878-
t.Fatalf("len(ToolCalls) = %d, want 1", len(out.ToolCalls))
879-
}
880-
if out.ToolCalls[0].Name != "read_file" {
881-
t.Fatalf("ToolCalls[0].Name = %q, want %q", out.ToolCalls[0].Name, "read_file")
882-
}
883-
if out.ToolCalls[0].Arguments["path"] != "/my/file.txt" {
884-
t.Fatalf("ToolCalls[0].Arguments[path] = %v, want /my/file.txt", out.ToolCalls[0].Arguments["path"])
885-
}
886-
// Even though it was repaired, the finish reason should still be truncated because the LLM originally returned length or we truncated it?
887-
// Actually, if finish_reason was "length", parseResponse will set finishReason to "truncated" anyway.
888-
if out.FinishReason != "truncated" {
889-
t.Fatalf("FinishReason = %q, want %q", out.FinishReason, "truncated")
890-
}
891-
}

pkg/providers/types.go

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,14 +37,6 @@ type StatefulProvider interface {
3737
Close()
3838
}
3939

40-
// EmbedProvider is an optional interface for providers that support text embeddings.
41-
// Not all providers implement this; use a type assertion to check.
42-
type EmbedProvider interface {
43-
// Embed converts text into a float32 vector using the given embedding model.
44-
// Returns an error if the provider does not support embeddings or the call fails.
45-
Embed(ctx context.Context, text string, model string) ([]float32, error)
46-
}
47-
4840
// ThinkingCapable is an optional interface for providers that support
4941
// extended thinking (e.g. Anthropic). Used by the agent loop to warn
5042
// when thinking_level is configured but the active provider cannot use it.

0 commit comments

Comments
 (0)