Root cause: internal/agent/loop_context.go re-applied tenant scoping
(TenantLayer/TenantID) on top of l.dataDir, which was already
tenant-scoped upstream in internal/agent/resolver.go, producing paths
like /app/workspace/tenants/<slug>/tenants/<slug>/teams/<teamID>
instead of the correct /app/workspace/tenants/<slug>/teams/<teamID>.
Also consolidates all previously-duplicated tenant-path-joining logic
(config.TenantDataDir, config.TenantWorkspace, tools.TenantLayer,
workspace.tenantPath) into a single canonical config.TenantScopedDir
function in internal/config/tenant_paths.go, with all other
implementations now delegating to it.
Co-authored-by: Bruno Clermont <bruno.clermont@gmail.com>
Replace inline workspace path computation in loop_context.go with composable
WorkspaceLayer pipeline (tenant → team → project → user/chat). Each layer is
a pure function that appends a path segment or is a no-op.
- New workspace_resolver.go: ResolveWorkspace, TenantLayer, TeamLayer,
ProjectLayer (future), UserChatLayer, SanitizePathSegment
- 16 unit tests covering all layer combinations
- Migrate loop_context.go, loop_history.go, team_tasks_mutations.go
- Move sanitizePathSegment from agent to tools package (exported)
- Zero behavior change — identical paths for all scenarios