Skip to content

feat(todo): add shared: team to scope a todo list to one team - #4782

Draft
steilerDev wants to merge 4 commits into
docker:mainfrom
steilerDev:feat/todo-shared-team
Draft

steilerDev wants to merge 4 commits into
docker:mainfrom
steilerDev:feat/todo-shared-team

Conversation

@steilerDev

@steilerDev steilerDev commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Problem

shared: true on the todo toolset returns a process-wide singleton. In the TUI every tab is a separate team load inside one process, so all tabs read and write the same todo list. There is no way to let the agents of one team share a list without also sharing it across sessions.

Change

shared now also accepts team:

Value Todo list is shared by
false (default) nothing, one list per agent (unchanged)
team the agents of one loaded team: root, sub-agents, background agents. Separate team loads (TUI tabs) get their own list; sessions that reuse one loaded team (serve a2a, serve mcp) share it
true the whole process, across sessions (unchanged)
  • Config (latest only): Toolset.Shared is now SharedScope. It unmarshals true, false and "team" (YAML and JSON) and marshals back to the same spelling, so CloneThroughJSON and upgrades from older versions keep working. Older config types are untouched.
  • Scope: tools.WithTeamScope / tools.TeamScoped keep per-load instances on the context. LoadWithConfig opens a scope for the root load; external agents imported into the team inherit it instead of opening their own.
  • Browser (wasm): team goes through the same context-aware team scope; true keeps its per-registry list. The two are separate lists.
  • todo: new todo.Creator (registered in teamloader/toolsets) resolves the scope. CreateToolSet keeps its signature. Outside a team load, team behaves like an unshared toolset.
  • Validation: shared is still todo-only; the schema description previously said "for think tool", which was wrong.
  • Schema, docs/tools/todo, and a new examples/shared-todo-team.yaml.

Testing

  • pkg/tools: scope isolation and the no-scope fallback.
  • pkg/config/latest: parse, invalid values, JSON/YAML round trip, marshalled spelling, team on a non-todo toolset.
  • pkg/tools/builtin/todo: every scope value.
  • cmd/wasm: team and true used together stay separate (fails on the previous creator).
  • pkg/teamloader: a real LoadWithConfig with two sub-agents, checking false / team / true across two loads (two tabs).
  • golangci-lint v2.14.0, go run ./lint ., go mod tidy --diff and the GOOS=js tests for cmd/wasm and todo pass locally.
  • go test ./... passes except four SSRF tests (*RejectsLocalAddresses, TestFetch_DefaultRefusesNonPublicAddresses) that also fail on unmodified main in my sandbox, where an outbound proxy intercepts the requests.

Notes

  • true keeps meaning "whole process". I did not change its behavior, to avoid breaking anyone relying on cross-session sharing.
  • think is unaffected: shared was already rejected for non-todo toolsets.

🤖 Generated with Claude Code

@steilerDev
steilerDev requested a review from a team as a code owner October 31, 2026 09:07
@aheritier aheritier added area/config For configuration parsing, YAML, environment variables area/core Core agent runtime, session management area/docs Documentation changes area/tools For features/issues/fixes related to the usage of built-in and MCP tools kind/feat PR adds a new feature (maps to feat:). Use on PRs only. labels Oct 31, 2026
@aheritier

Copy link
Copy Markdown
Collaborator

@steilerDev could you configure Claude code to not co-author your commits please? We don't care about the harness used (even if docker-agent is the best). Thanks

@aheritier aheritier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed config parsing/migration, native and browser todo creation, team-scope propagation, and session lifetimes against the PR description. See inline comments.

CI operational status: verified gate SUCCESS at 4ecf631. The PR is non-draft and conflict-free; GitHub reports merge state BLOCKED.

Issue context: needs-decision—no linked GitHub/Jira acceptance criteria were provided or discovered. The PR description supplied the review checklist; all PR comments, reviews, and threads were retrieved.

Validation: reviewed source at the verified head through GitHub. Local tests were not run because the checkout is at a different commit.

Comment thread cmd/wasm/toolsets.go Outdated
"random": teamloader.Creator(random.CreateToolSet),
"todo": teamloader.CreatorFromToolset(func(ts latest.Toolset) (tools.ToolSet, error) {
if ts.Shared {
if ts.Shared != latest.SharedScopeNone {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] Keep team-scoped and legacy shared todos separate in the browser. Confidence: high.

Here is the failing case: within one browser registry, create a todo toolset with SharedScopeTeam and another with SharedScopeProcess. Both return the same sharedTodos() instance, so a todo created through either is visible and mutable through the other. Native todo.Creator keeps those stores separate, and TestCreator_SharedScopes explicitly asserts that a team instance differs from a process instance.

Handle SharedScopeTeam through the context-aware team scope, retaining the existing browser behavior for SharedScopeProcess. Add a browser regression test covering both values together; the existing browser test exercises only SharedScopeProcess.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 39631af: the browser todo creator now sends team through tools.TeamScoped and keeps true on the per-registry sharedTodos(). Added a cmd/wasm test that uses both values in one registry and checks they stay separate; I confirmed it fails against the previous creator.

Comment thread docs/tools/todo/index.md Outdated
| Value | Todo list is shared by |
| ---------------- | ------------------------------------------------------------------------------------------------------------ |
| `false` | nothing: each agent has its own list |
| `team` | the agents of one team: root, its sub-agents and background agents. Each session or tab has its own list |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[should-fix] Describe isolation per team load, not unconditionally per session. Confidence: high.

The new scope is created only in LoadWithConfig, and its todo instance is retained by the loaded agents. In serve a2a, pkg/a2a/server.go:66 loads one team at startup and pkg/a2a/adapter.go:102–119 creates distinct sessions/runtimes using that same team. Likewise, pkg/mcp/server.go:278–280 creates a fresh session for each invocation but reuses the loaded team. Consequently, shared: team todos remain visible across those conversations.

Qualify this table row and the recommendation below it: separate team loads, such as TUI tabs, are isolated; sessions using the same loaded team are not. If per-conversation isolation is intended for these servers too, it needs session-scoped storage and regression coverage rather than only a load-scoped cache.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, I verified serve a2a and serve mcp both reuse one loaded team across sessions. Fixed in 39631af: the table row and the recommendation now scope team to a team load, say TUI tabs are isolated, and state that sessions reusing one loaded team share its list. Same wording in the schema description and examples/README.md. I did not add session-scoped storage; that seems like a separate change if you want per-conversation isolation for those servers.

@aheritier
aheritier marked this pull request as draft October 31, 2026 12:08
`shared: true` returns a process-wide singleton, so in the TUI every tab
(a separate team load in the same process) reads and writes the same todo
list. There was no way to share a list between the agents of one team
without also sharing it across sessions.

`shared` now also accepts `team`: all agents of one team load share a list,
while each session or tab gets its own. `true` keeps its meaning and
`false` stays per-agent.

- latest config: `Toolset.Shared` becomes `SharedScope`, which unmarshals
  `true`, `false` and `"team"` and marshals back to the same spelling so
  JSON clones and older-version upgrades keep working.
- tools: `WithTeamScope` / `TeamScoped` hold the per-load instances in ctx.
- teamloader: the root load opens a team scope; external agents joining the
  team inherit it.
- todo: `Creator` is the registry creator that resolves the scope.
  `CreateToolSet` is unchanged apart from the new type.
- schema, docs and an example updated.
…docs to team loads

- wasm: the browser todo creator mapped every non-empty `shared` value to
  one per-registry list, so `team` and `true` read and wrote the same todos.
  `team` now goes through the context-aware team scope; `true` keeps its
  per-registry behavior. Add a regression test using both values together.
- docs: `team` is scoped to a team load, not a session. TUI tabs are
  isolated, but sessions that reuse one loaded team (`serve a2a`,
  `serve mcp`) share that team's list. Same wording in the schema
  description and the examples index.
@steilerDev
steilerDev force-pushed the feat/todo-shared-team branch from 39631af to 43e2baf Compare November 2, 2026 10:37
@aheritier aheritier added the status/needs-rebase PR has merge conflicts or is out of date with main label Nov 2, 2026
@aheritier

Copy link
Copy Markdown
Collaborator

👋 This PR has merge conflicts with the base branch. Please rebase or merge the latest base branch and resolve them. I've moved it to draft and added status/needs-rebase; it'll be picked back up automatically once the conflicts are cleared.

Keep both the team scope opened in LoadWithConfig and main's cleanup of
loaded agents on a failed load.
@aheritier aheritier removed the status/needs-rebase PR has merge conflicts or is out of date with main label Nov 2, 2026
@steilerDev
steilerDev marked this pull request as ready for review November 3, 2026 20:59
@steilerDev

Copy link
Copy Markdown
Contributor Author

@aheritier thanks, and sorry for the noise.

  • Co-author trailer: removed. I rewrote the first commit and force-pushed, so none of the PR's commits carry a Co-Authored-By line anymore (all three are signed).
  • Both inline items are addressed in 43e2baf: the browser creator keeps team and shared: true as separate lists (with a cmd/wasm regression test that fails on the old creator), and the docs/schema now scope team to a team load rather than a session.
  • Conflicts: resolved by merging main in 044691d. It still merges cleanly against current main, and CI is green.

Could you take another look when you have a moment? The CHANGES_REQUESTED review is from before these changes.

@steilerDev
steilerDev requested a review from aheritier November 4, 2026 08:28

@aheritier aheritier left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed all 18 changed paths against the PR description's scope, compatibility, browser, validation, documentation, and test claims. See inline comment.

CI operational status: verified gate SUCCESS at 044691d; lint and Linux/Windows tests passed. test-race was skipped. The PR is non-draft and conflict-free; GitHub reports merge state BLOCKED. Local tests were not run because the checkout is at a different commit.

Issue context: needs-decision—no explicit linked GitHub/Jira acceptance criteria were supplied or discovered, including closing-issue references. The PR description supplied the review checklist. PR comments, reviews, and thread state were retrieved completely.

Prior threads:

  • 4431649598: addressed; the browser now separates team and legacy shared stores, with a regression test exercising both together.
  • 4431649600: addressed; documentation now describes isolation per team load and explicitly covers sessions reusing a loaded team.

Open questions: Confirm that the PR description is the complete acceptance criteria, or provide the governing issue.


// External agents join the importing team, so only the root load opens a scope.
if externalDepthFromContext(ctx) == 0 {
ctx = tools.WithTeamScope(ctx)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[blocking] Preserve the team scope when rebuilding background worktree agents. Confidence: high.

The scope installed here exists only on the load's local context. When a background agent uses worktree: true, pkg/runtime/background_worktree.go:84 calls r.team.ForWorkingDir(ctx, wt.Dir) with the runtime context. That rebuild reaches the working-directory factory in pkg/teamloader/teamloader.go:552–556, whose ctx parameter replaces the scoped load context when calling agentToolOptions. Nothing restores the team scope, so todo.Creator calls TeamScoped without a scope and gets a fresh private list for every rebuilt agent.

Consequently, a worktree background agent cannot see its parent's team todos, and even agents within the rebuilt team do not share a list. This contradicts the new mode's documented inclusion of background agents; the legacy process-scoped mode does not lose its shared store along this path.

Retain and reapply the originating team's scope during working-directory rebuilds without sharing it across unrelated loads. Add a regression test that creates a team todo, rebuilds through ForWorkingDir using an ordinary runtime context, and verifies visibility and updates across the parent and rebuilt agents.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 637a5bd: the working-dir factory now rejoins the scope of the load it was created in (tools.InheritTeamScope), so agents rebuilt through Team.ForWorkingDir on the runtime context share the parent's shared: team list, and other loads stay isolated.

Regression test: TestLoadWithConfig_SharedTodoTeamSurvivesWorkingDirRebuild rebuilds via ForWorkingDir on an ordinary context and checks the parent and rebuilt agents share one list while an unrelated load does not. It fails without the fix (rebuilt agents get private lists). I also added unit tests for InheritTeamScope.

Local run: all packages I could build pass except the SSRF tests (*RejectsLocalAddresses, TestFetch_DefaultRefusesNonPublicAddresses), pkg/skills TestProjectSearchDirs and reasoningblock, which fail identically on 044691d. The root package, cmd/root and e2e/* did not build in my environment (out of disk), so CI is the check for those.

@aheritier
aheritier marked this pull request as draft November 4, 2026 20:33
Worktree background agents rebuild the team with Team.ForWorkingDir on the
runtime's context, which carries no team scope, so todo.Creator gave every
rebuilt agent a private list. The working-dir factory now rejoins the scope
of the load it was created in (tools.InheritTeamScope), so rebuilt agents
share the parent team's list and other loads stay isolated.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config For configuration parsing, YAML, environment variables area/core Core agent runtime, session management area/docs Documentation changes area/tools For features/issues/fixes related to the usage of built-in and MCP tools kind/feat PR adds a new feature (maps to feat:). Use on PRs only.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants