Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
78 commits
Select commit Hold shift + click to select a range
e7de26e
feat(teams): implement Agent Teams architecture
lppp04808 Feb 28, 2026
429ce66
feat(teams): implement heterogeneous agent model routing
lppp04808 Feb 28, 2026
efb9dc2
feat(teams): add model tags system for capability-aware agent routing
lppp04808 Feb 28, 2026
33c759f
feat(teams): improve team activation and resilience
lppp04808 Feb 28, 2026
06c69c9
Merge branch 'main' into feat/team
lppp04808 Feb 28, 2026
fdc6d35
feat(teams): add produces field for automatic QA reviewer injection
lppp04808 Feb 28, 2026
1548691
Merge feat/team: Agent Teams architecture
lppp04808 Feb 28, 2026
006ace5
Merge branch 'sipeed:main' into main
lppp04808 Feb 28, 2026
614a010
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Feb 28, 2026
311ff51
Merge branch 'sipeed:main' into main
lppp04808 Mar 1, 2026
1f36e0b
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Mar 1, 2026
07c2455
Merge branch 'sipeed:main' into main
lppp04808 Mar 1, 2026
3a4ea16
Merge branch 'sipeed:main' into main
lppp04808 Mar 1, 2026
6918da3
Merge branch 'sipeed:main' into main
lppp04808 Mar 2, 2026
70b4dff
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Mar 2, 2026
3cd192d
Merge branch 'main' into main
lppp04808 Mar 2, 2026
389a40b
fix: fix conflicts
lppp04808 Mar 2, 2026
f3d778a
Update filesystem_test.go
lppp04808 Mar 2, 2026
28dca94
fix: conflicts
lppp04808 Mar 2, 2026
cb9107d
Merge branch 'sipeed:main' into main
lppp04808 Mar 2, 2026
7826b50
fix: conflicts
lppp04808 Mar 2, 2026
8d38680
Merge remote-tracking branch 'refs/remotes/myfork/main'
lppp04808 Mar 2, 2026
e0ea001
Merge branch 'sipeed:main' into main
lppp04808 Mar 3, 2026
f1f160e
Merge remote-tracking branch 'refs/remotes/myfork/main'
lppp04808 Mar 3, 2026
8c7c6f5
feat(memory): implement pure-go dynamic vector memory search without …
lppp04808 Mar 3, 2026
a3fa5d0
Merge branch 'sipeed:main' into main
lppp04808 Mar 3, 2026
a4039ec
Merge branch 'sipeed:main' into main
lppp04808 Mar 3, 2026
ea4a99b
Merge branch 'sipeed:main' into main
lppp04808 Mar 3, 2026
ec1a4bc
Merge branch 'sipeed:main' into main
lppp04808 Mar 3, 2026
22f10c6
Merge branch 'main' into main
lppp04808 Mar 4, 2026
ff13602
fix(agent): align context cache with skillDirs root and fix undefined…
lppp04808 Mar 4, 2026
3cc3f21
Merge branch 'sipeed:main' into main
lppp04808 Mar 4, 2026
b3be28d
Merge branch 'main' into main
lppp04808 Mar 4, 2026
08194b8
Merge branch 'main' into main
lppp04808 Mar 5, 2026
55fdc09
Merge branch 'sipeed:main' into main
lppp04808 Mar 5, 2026
9d2a67d
Merge branch 'sipeed:main' into main
lppp04808 Mar 5, 2026
fae2dff
Merge branch 'main' into main
lppp04808 Mar 9, 2026
6693e1f
fix
lppp04808 Mar 9, 2026
1212880
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Mar 9, 2026
8614a11
fix(agent): update subagent manager configuration and dependencies
lppp04808 Mar 9, 2026
d8c0276
Merge branch 'sipeed:main' into main
lppp04808 Mar 9, 2026
e49c801
Merge branch 'main' into main
lppp04808 Mar 10, 2026
eebb257
fix(tools): implement fileSystem Open for ConcurrentFS and fix test c…
lppp04808 Mar 10, 2026
8566ff6
build: remove sqlite vector memory to fix cross-compilation
lppp04808 Mar 10, 2026
8a98520
trigger cla recheck
lppp04808 Mar 10, 2026
7d8aaeb
Merge branch 'sipeed:main' into main
lppp04808 Mar 10, 2026
f06d044
Merge branch 'main' into main
lppp04808 Mar 10, 2026
da361b9
Merge branch 'main' into main
lppp04808 Mar 10, 2026
930954f
fix: conflict
lppp04808 Mar 10, 2026
51ac004
Merge branch 'sipeed:main' into main
lppp04808 Mar 10, 2026
c9613ab
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
2f4d10d
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
8438f8a
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
d21a0d6
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
551f091
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
21ba797
Merge branch 'sipeed:main' into main
lppp04808 Mar 11, 2026
55dd322
merge
lppp04808 Mar 12, 2026
f5f9916
feat(team): centralize team tool config with allowed_models, limits, …
lppp04808 Mar 12, 2026
bccdc92
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Mar 12, 2026
0a353e6
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
f12638c
feat(team): refine configuration, fix critical bugs, and update docum…
lppp04808 Mar 12, 2026
0eca5d9
Merge remote-tracking branch 'refs/remotes/origin/main'
lppp04808 Mar 12, 2026
40b61c6
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
e40752a
fix(agent): use feat/team context.go version, fix GetMemoryContext si…
lppp04808 Mar 12, 2026
a0d91c4
feat(team): integrate refined team tool and align with upstream
lppp04808 Mar 12, 2026
c05cf7c
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
5b80e07
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
fe51853
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
bac9a1c
refactor(tools): refine team tool configuration and add unit tests
lppp04808 Mar 12, 2026
d22ca85
fix(tools): preserve context memory and thought signatures in toolloop
lppp04808 Mar 12, 2026
c2cb6f5
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
3aa7751
Merge branch 'sipeed:main' into main
lppp04808 Mar 12, 2026
a1c9058
Merge branch 'sipeed:main' into main
lppp04808 Mar 13, 2026
a930b39
Merge branch 'sipeed:main' into main
lppp04808 Mar 13, 2026
0d8a6ba
Merge branch 'sipeed:main' into main
lppp04808 Mar 13, 2026
bda8f12
Merge branch 'sipeed:main' into main
lppp04808 Mar 13, 2026
365f891
Merge branch 'sipeed:main' into main
lppp04808 Mar 13, 2026
684accb
Merge branch 'sipeed:main' into main
lppp04808 Mar 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
45 changes: 43 additions & 2 deletions docs/tools_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ PicoClaw's tools configuration is located in the `tools` field of `config.json`.
},
"skills": {
...
},
"team": {
...
}
}
}
Expand Down Expand Up @@ -302,6 +305,44 @@ The skills tool configures skill discovery and installation via registries like
}
```

## Team Tool

The team tool allows PicoClaw to orchestrate multiple agents using strategies like Sequential, Parallel, DAG, and Evaluator-Optimizer.

### Global Config

| Config | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `enabled` | bool | false | Enable the Team tool |
| `max_members` | int | 5 | Max agents in a single team |
| `max_team_tokens` | int | 0 | Hard ceiling for total token usage per team call (0 = no limit) |
| `max_evaluator_loops`| int | 5 | Max retries for `evaluator_optimizer` strategy |
| `max_timeout_minutes`| int | 15 | Max execution time for team tasks |
| `max_context_runes` | int | 8000 | Max dependency context size between workers |
| `disable_auto_reviewer` | bool | false | Skip automatic QA reviewer step |
| `reviewer_model` | string | - | Specifically assigned model for the reviewer step |
| `allowed_strategies` | array | [] | Whitelist of allowed strategies (empty = all) |
| `allowed_models` | array | [] | Whitelist of models team members can use |

### Configuration Example

```json
{
"tools": {
"team": {
"enabled": true,
"max_members": 5,
"max_team_tokens": 100000,
"reviewer_model": "gpt-4o-mini",
"allowed_models": [
{ "name": "gpt-4o", "tags": ["vision", "code"] },
{ "name": "claude-3-5-sonnet", "tags": ["precise"] }
]
}
}
}
```

## Environment Variables

All configuration options can be overridden via environment variables with the format `PICOCLAW_TOOLS_<SECTION>_<KEY>`:
Expand All @@ -312,6 +353,6 @@ For example:
- `PICOCLAW_TOOLS_EXEC_ENABLE_DENY_PATTERNS=false`
- `PICOCLAW_TOOLS_CRON_EXEC_TIMEOUT_MINUTES=10`
- `PICOCLAW_TOOLS_MCP_ENABLED=true`
- `PICOCLAW_TOOLS_TEAM_MAX_TEAM_TOKENS=50000`

Note: Nested map-style config (for example `tools.mcp.servers.<name>.*`) is configured in `config.json` rather than
environment variables.
Note: Nested map-style config (for example `tools.mcp.servers.<name>.*` or `tools.team.allowed_models`) is configured in `config.json` rather than environment variables.
4 changes: 3 additions & 1 deletion pkg/agent/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ Your workspace is at: %s

4. **Context summaries** - Conversation summaries provided as context are approximate references only. They may be incomplete or outdated. Always defer to explicit user instructions over summary content.

5. **Team delegation** - For any task that is non-trivial, multi-step, or involves distinct concerns (e.g. "convert React to Vue", "build a feature", "analyze and report"), you MUST use the 'team' tool to delegate and parallelize. Do NOT attempt to handle complex tasks inline by calling tools one by one yourself. Decompose first, delegate second, then report the outcome.

%s`,
version, workspacePath, workspacePath, workspacePath, workspacePath, workspacePath, toolDiscovery)
}
Expand All @@ -123,7 +125,7 @@ func (cb *ContextBuilder) getDiscoveryRule() string {
}

return fmt.Sprintf(
`5. **Tool Discovery** - Your visible tools are limited to save memory, but a vast hidden library exists. If you lack the right tool for a task, BEFORE giving up, you MUST search using the %s tool. Do not refuse a request unless the search returns nothing. Found tools will temporarily unlock for your next turn.`,
`6. **Tool Discovery** - Your visible tools are limited to save memory, but a vast hidden library exists. If you lack the right tool for a task, BEFORE giving up, you MUST search using the %s tool. Do not refuse a request unless the search returns nothing. Found tools will temporarily unlock for your next turn.`,
strings.Join(toolNames, " or "),
)
}
Expand Down
18 changes: 9 additions & 9 deletions pkg/agent/context_cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,11 @@ func TestMtimeAutoInvalidation(t *testing.T) {
checkField: "Updated Identity",
},
{
name: "memory file change",
file: "memory/MEMORY.md",
contentV1: "# Memory\nUser likes Go.",
contentV2: "# Memory\nUser likes Rust.",
checkField: "User likes Rust",
name: "another bootstrap file change",
file: "SOUL.md",
contentV1: "# Original Soul",
contentV2: "# Updated Soul",
checkField: "Updated Soul",
},
}

Expand Down Expand Up @@ -286,10 +286,10 @@ func TestNewFileCreationInvalidatesCache(t *testing.T) {
checkField: "Be kind and helpful",
},
{
name: "new memory file",
file: "memory/MEMORY.md",
content: "# Memory\nUser prefers dark mode.",
checkField: "User prefers dark mode",
name: "new agents file",
file: "AGENTS.md",
content: "# Agents\nCustom agent definition.",
checkField: "Custom agent definition",
},
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/agent/instance.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package agent

import (

"context"
"fmt"
"log"
Expand Down Expand Up @@ -117,6 +118,7 @@ func NewAgentInstance(
skillsFilter = agentCfg.Skills
}


maxIter := defaults.MaxToolIterations
if maxIter == 0 {
maxIter = 20
Expand Down
26 changes: 25 additions & 1 deletion pkg/agent/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,9 +223,33 @@ func registerSharedTools(
}

// Spawn tool with allowlist checker
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Candidates, agent.Workspace, cfg.Tools.Team, msgBus)
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
spawnTool := tools.NewSpawnTool(subagentManager)
currentAgentID := agentID
spawnTool.SetAllowlistChecker(func(targetAgentID string) bool {
return registry.CanSpawnSubagent(currentAgentID, targetAgentID)
})
agent.Tools.Register(spawnTool)

teamTool := tools.NewTeamTool(subagentManager, cfg)
if cfg.Tools.IsToolEnabled("team") {
agent.Tools.Register(teamTool)
}

spawnSubAgentTool := tools.NewSpawnSubAgentTool(subagentManager)
if cfg.Tools.IsToolEnabled("spawn_sub_agent") {
agent.Tools.Register(spawnSubAgentTool)
}

// Direction 3: Hierarchical Decomposition.
// Share the fully-built registry (which includes team, spawn_sub_agent, etc.) back
// to the subagent manager so that all workers spawned by this agent also inherit
// the full toolset — enabling sub-agents to recursively call 'team' themselves.
subagentManager.SetTools(agent.Tools)
if cfg.Tools.IsToolEnabled("spawn") {
if cfg.Tools.IsToolEnabled("subagent") {
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Workspace)
subagentManager := tools.NewSubagentManager(provider, agent.Model, agent.Candidates, agent.Workspace, cfg.Tools.Team, msgBus)
subagentManager.SetLLMOptions(agent.MaxTokens, agent.Temperature)
spawnTool := tools.NewSpawnTool(subagentManager)
currentAgentID := agentID
Expand Down
3 changes: 2 additions & 1 deletion pkg/agent/memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,9 +130,10 @@ func (ms *MemoryStore) GetRecentDailyNotes(days int) string {
}

// GetMemoryContext returns formatted memory context for the agent prompt.
// Includes long-term memory and recent daily notes.
// It loads the full MEMORY.md.
func (ms *MemoryStore) GetMemoryContext() string {
longTerm := ms.ReadLongTerm()

recentNotes := ms.GetRecentDailyNotes(3)

if longTerm == "" && recentNotes == "" {
Expand Down
20 changes: 20 additions & 0 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ func (f *FlexibleStringSlice) UnmarshalText(text []byte) error {
return nil
}

type TeamModelConfig struct {
Name string `json:"name"`
Tags []string `json:"tags,omitempty"`
}

type TeamToolsConfig struct {
ToolConfig
MaxMembers int `json:"max_members"`
MaxTeamTokens int `json:"max_team_tokens"`
MaxEvaluatorLoops int `json:"max_evaluator_loops"`
MaxTimeoutMinutes int `json:"max_timeout_minutes"`
MaxContextRunes int `json:"max_context_runes"`
DisableAutoReviewer bool `json:"disable_auto_reviewer"`
ReviewerModel string `json:"reviewer_model"`
AllowedStrategies []string `json:"allowed_strategies"`
AllowedModels []TeamModelConfig `json:"allowed_models"`
}

type Config struct {
Agents AgentsConfig `json:"agents"`
Bindings []AgentBinding `json:"bindings,omitempty"`
Expand Down Expand Up @@ -750,6 +768,8 @@ type ToolsConfig struct {
Spawn ToolConfig `json:"spawn" envPrefix:"PICOCLAW_TOOLS_SPAWN_"`
SPI ToolConfig `json:"spi" envPrefix:"PICOCLAW_TOOLS_SPI_"`
Subagent ToolConfig `json:"subagent" envPrefix:"PICOCLAW_TOOLS_SUBAGENT_"`
SpawnSubAgent ToolConfig `json:"spawn_sub_agent" envPrefix:"PICOCLAW_TOOLS_SPAWN_SUB_AGENT_"`
Team TeamToolsConfig `json:"team" envPrefix:"PICOCLAW_TOOLS_TEAM_"`
WebFetch ToolConfig `json:"web_fetch" envPrefix:"PICOCLAW_TOOLS_WEB_FETCH_"`
WriteFile ToolConfig `json:"write_file" envPrefix:"PICOCLAW_TOOLS_WRITE_FILE_"`
}
Expand Down
42 changes: 20 additions & 22 deletions pkg/tools/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package tools

import (
"context"
"errors"
"fmt"
"io/fs"
"regexp"
"strings"
)
Expand Down Expand Up @@ -75,6 +73,12 @@ func (t *EditFileTool) Execute(ctx context.Context, args map[string]any) *ToolRe
return SilentResult(fmt.Sprintf("File edited: %s", path))
}

func (t *EditFileTool) UpgradeToConcurrent() Tool {
return &EditFileTool{
fs: &ConcurrentFS{baseFS: t.fs},
}
}

type AppendFileTool struct {
fs fileSystem
}
Expand Down Expand Up @@ -129,31 +133,25 @@ func (t *AppendFileTool) Execute(ctx context.Context, args map[string]any) *Tool
return SilentResult(fmt.Sprintf("Appended to %s", path))
}

// editFile reads the file via sysFs, performs the replacement, and writes back.
// It uses a fileSystem interface, allowing the same logic for both restricted and unrestricted modes.
func editFile(sysFs fileSystem, path, oldText, newText string) error {
content, err := sysFs.ReadFile(path)
if err != nil {
return err
}

newContent, err := replaceEditContent(content, oldText, newText)
if err != nil {
return err
func (t *AppendFileTool) UpgradeToConcurrent() Tool {
return &AppendFileTool{
fs: &ConcurrentFS{baseFS: t.fs},
}
}

return sysFs.WriteFile(path, newContent)
// editFile reads the file via sysFs, performs the replacement, and writes back atomically.
func editFile(sysFs fileSystem, path, oldText, newText string) error {
return sysFs.EditFile(path, func(content []byte) ([]byte, error) {
return replaceEditContent(content, oldText, newText)
})
}

// appendFile reads the existing content (if any) via sysFs, appends new content, and writes back.
// appendFile reads the existing content (if any) via sysFs, appends new content, and writes back atomically.
func appendFile(sysFs fileSystem, path, appendContent string) error {
content, err := sysFs.ReadFile(path)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return err
}

newContent := append(content, []byte(appendContent)...)
return sysFs.WriteFile(path, newContent)
return sysFs.EditFile(path, func(content []byte) ([]byte, error) {
newContent := append(content, []byte(appendContent)...)
return newContent, nil
})
}

// replaceEditContent handles the core logic of finding and replacing a single occurrence of oldText.
Expand Down
Loading