Re-applies the tenant-isolation extension from the deleted pkg/agent/loop.go
to upstream's split agent_*.go layout. Phase 1 plumbs the protocol; Phase 2
(future PR) wires effective sessions / provider / context per turn.
What's added:
- New file pkg/agent/agent_tenant.go (kept isolated for sync friendliness):
- extractTenantOverrides reads workspace_override, config_dir,
allowed_tools, allowed_skills from msg.Context.Raw
- Validates workspace_override and config_dir resolve within the
workspace_root boundary set in agents.defaults; rejects path escape
- Fails closed when any override is present but workspace_root is unset,
so tenants cannot bypass the security boundary
- applyTo copies the overrides onto processOptions
- logIfPresent logs a single line summarising tenant routing per turn
- processOptions in agent.go gains four override fields (WorkspaceOverride,
ConfigDir, AllowedTools, AllowedSkills)
- agent_message.go processMessage extracts and applies the overrides right
after building processOptions
- agent_tenant_test.go covers the happy path, escape rejection, missing
boundary rejection, and CSV parsing
Phase 2 will introduce effSessions, effContextBuilder, effProvider, effModel
on processOptions and thread them through pipeline_llm/turn_state/context_*
so each tenant turn runs against an isolated session store, context builder,
and provider credential set. Phase 2 is deferred because:
- MagicForm doesn't currently send webhook traffic (verified), so phase 1's
protocol-level plumbing has no live consumer that requires the swap yet.
- Phase 2 needs design discussion (per-tenant credential storage, session
backend selection) that warrants its own focused PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>