Files
kandev/docs/decisions/2026-08-13-dynamic-agent-profile-routing.md
T
2026-08-21 22:04:17 +01:00

301 lines
14 KiB
Markdown

# ADR-2026-08-13-dynamic-agent-profile-routing: Unify Provider Routing Behind Dynamic Agent Profiles
**Status:** accepted (amended 2026-08-17)
**Date:** 2026-08-13
**Area:** backend, frontend, protocol, workflow
## Amendment (2026-08-17)
[ADR-2026-08-17-provider-error-classes-and-policies](2026-08-17-provider-error-classes-and-policies.md)
replaces generic dynamic candidate actions with separate transient and hard
policies. Each policy can wait for a trusted near reset, retry with bounded
exponential backoff, then skip or stop. The shared routing ownership and
continuation boundaries in this decision remain unchanged.
## Context
Kandev's first provider-routing design belongs to Office workspaces. It maps
model tiers to concrete execution profiles, tracks provider health, and falls
back between providers. Kanban tasks need the same capability, including one
logical chat session while the concrete provider changes.
Keeping routing in Office creates two policy engines and two settings surfaces.
It also creates different recovery behavior for the same user intent. Routing
then depends on where a task runs rather than on the profile that
the user selected. Office has not been enabled for users, so its ownership
boundary can change before compatibility is required.
ACP gives Kandev a common transport, but a provider-native session cannot be
loaded by a different provider. A routed agent therefore needs a Kandev-owned
logical session above several provider-native ACP sessions. The initial feature
must work from provider errors and shared health without new plugin contracts.
Subscription and cost routing are a separate future feature.
## Decision
### Dynamic profiles own routing
Add a permanent built-in virtual agent family with the canonical ID `dynamic`.
The family is always registered, cannot be disabled or uninstalled, does not
advertise an ACP command, and is excluded from concrete-agent probing. User
profiles created under that family are dynamic profiles. This preserves the
existing `agent_profiles.agent_id` ownership invariant without making a
removable synthetic agent the parent of durable routing policy.
A dynamic profile is a virtual agent configuration and is not itself a provider
CLI launch configuration. It owns:
- a user-defined name and an ordered set of concrete agent-profile candidates,
- actions for classified provider errors, and
- route-health, circuit-breaker, probing, and recovery policy.
A dynamic candidate must be a concrete, launchable profile. Rich Office
identities and dynamic profiles cannot be candidates. An Office identity's
execution binding can select a concrete or dynamic profile because that binding
is not a candidate edge.
Names such as Frontier, Balanced, Economy, Review, or Security Review express
the user's intent. Kandev does not persist a class or tier field and does not
interpret those names.
Dynamic profiles are configured with the normal agent settings. Office no
longer owns a provider-routing settings page or a workspace-specific routing
policy.
### Profile selection is transparent to callers
Task creation, workflow steps, Office execution bindings, utility agents, and
other profile consumers continue to pass one selected agent-profile ID. A
shared execution resolver inspects that profile behind the selection boundary:
- a concrete profile resolves directly to its own launch configuration, and
- a dynamic profile resolves to the conductor, which selects and records a
concrete execution profile before launching downstream ACP.
Callers do not branch on profile kind and do not supply a concrete
`execution_profile_id` for a dynamic profile. Internally,
`execution_profile_id` always identifies the concrete profile that actually
launched. It equals the selected profile ID for a concrete selection and differs
for a dynamic selection. This distinction keeps the abstraction transparent
without treating the non-launchable virtual profile as a CLI configuration.
Kanban tasks and workflow steps can select a dynamic profile wherever they can
select an agent profile. An Office agent selects either a concrete or dynamic
profile as its execution agent profile. Office wake-reason policy can request a
different execution agent profile for a run, but it cannot define provider
order, mappings, health policy, or fallback rules outside the selected
dynamic profile.
Utility agents also accept concrete or dynamic profiles. A one-shot utility
invocation has no visible task-session tab, but it uses the same resolver and
dynamic conductor. Its durable call record retains the selected logical profile
and the concrete execution profile that produced the result.
### Keep identity, policy, and execution separate
A routed Office run has three distinct identities:
1. The Office agent identity owns its role, instructions, skills, permissions,
budget, hierarchy, and Office history.
2. The assigned dynamic profile owns routing policy and is the logical agent
shown for the task session.
3. The resolved concrete profile owns the provider CLI, account environment,
model, mode, ACP options, flags, permissions, and MCP configuration for the
current downstream session.
A routed Kanban run has the latter two identities. A concrete-profile run has
no separate routing identity.
The runtime and persistence contracts must carry these identities explicitly.
They must not infer one identity from another or construct a hybrid launch by
overlaying one provider's settings on another profile.
### The dynamic runtime is a virtual ACP conductor
The task backend, workflow scheduler, and task UI continue to see one Kandev
task session and one chat tab. A dynamic-profile runtime owns the downstream
ACP lifecycle behind that session.
When a route changes, the conductor:
1. settles or stops the failed downstream attempt,
2. persists the route transition and invalidates its provider-native resume
token for the successor,
3. starts a new ACP session with the selected concrete profile,
4. builds a bounded continuation prompt from durable task state, run state,
recent conversation, repository state, and an optional handoff summary, and
5. publishes a route-change event followed by an authoritative replacement of
models, modes, configuration options, and custom commands.
The provider-native session is not exposed as another Kandev task session and
does not create another tab. Same-profile restart can use that provider's
`session/load` or `session/resume`. Cross-profile continuation always starts a
fresh provider-native session.
These events are Kandev extensions around ACP, not claims that standard ACP can
import one provider's history into another provider.
### Routing policy follows profile kind
Provider-error classification remains shared and provider-neutral. The
selected profile kind chooses the recovery owner:
- A concrete profile uses conservative interactive same-profile recovery. It
never changes account, model, or provider automatically.
- A dynamic profile uses its configured retry and fallback policy in Kanban or
Office. Selecting it is prior authorization to use its eligible routes.
The dynamic conductor is the only owner of cross-candidate fallback. A concrete
profile with legacy `AutoFallback` enabled is not a valid dynamic candidate. An
explicit `FallbackModel` remains an intra-profile start-model policy and does
not select another dynamic candidate.
This replaces the earlier rule that Kanban and Office necessarily have
different routing owners.
### Profile edits use the next routing decision
Dynamic profile configuration is versioned. Normal edits affect a running
logical session at its next routing decision. They do not churn an otherwise
healthy active route. Disabling or removing the active candidate schedules a
reroute at the next safe turn boundary. Historical route decisions retain the
profile version that produced them.
### Logical identity and concrete attribution coexist
The Kanban tab and assistant author keep the dynamic profile's identity. In
Office, they keep the Office agent identity, such as CEO. Every assistant turn
stores immutable concrete profile, provider, model, and route generation
attribution and displays it as compact metadata. A route change also
creates a durable system row, while the live model, mode, options, and commands
always come from the active concrete route.
Turn attribution extends the immutable snapshot boundary from
[Attribute Runtime Configuration to Turns](2026-07-18-turn-configuration-snapshots.md),
historical messages never inherit the session's latest route.
### Health is shared by the resource that failed
Route health uses a scope-specific resource key. Provider outages use provider
scope. Quota failures use provider and credential-binding scope. Model evidence
adds model scope. Profile configuration errors use concrete-profile scope.
Each concrete launch adapter produces a versioned
`CredentialBindingDescriptor` after environment resolution. It includes the
canonical family, authentication mechanism, non-secret credential source,
executor namespace, authorization scope, and applicable workspace scope.
`CredentialBindingResolver` canonicalizes the descriptor and computes an
HMAC-SHA-256 fingerprint with a persistent Kandev installation key. It cannot
include raw credentials, credential-file contents, literal environment values,
`CommandPrefix`, CLI flags, models, prompts, or raw account IDs. If an adapter
cannot prove a shared binding, the resolver uses a conservative profile-scoped
identity.
A classified hard or long-horizon failure atomically opens the shared circuit.
Other dynamic sessions that reference the same resource skip it while the
circuit is open. One half-open attempt owns the probe lease. Successful probes
close the circuit, failed probes extend its deadline. Existing sessions on a
fallback route are not moved back automatically when an earlier candidate
recovers. Recovery affects new selections and explicit safe-boundary reroutes.
### Telemetry routing is a separate feature
This decision implements the virtual family, transparent resolver, fixed
candidate order, classified-error fallback, shared circuits and probes, the ACP
conductor, continuation, restart recovery, and Kanban, Office, and utility
integration. It does not add session-cost or subscription-usage plugin
contracts. It does not add telemetry-backed rules or turn interruption.
Those behaviors belong to the separate
[Dynamic Agent Telemetry Routing](../specs/agents/dynamic-agent-telemetry-routing.md)
feature and implementation package. An implementation of this decision must not
implement that deferred package.
### Legacy Office routing data is retained but inactive
Office has not been released as an enabled user workflow, so Kandev does not
translate legacy Office workspace routing into dynamic profiles. Existing
Office routing tables and rows remain intact across upgrade. New code stops
displaying and mutating those settings, and users configure dynamic profiles
when they want routing. A future cleanup requires a separate retention decision,
but this migration does not drop or rewrite the legacy rows.
The handoff removes the live Office `StartTaskWithRoute` and `RouteOverride`
path, including legacy model, flag, and environment overlays. Dynamic routing
always launches one complete concrete profile. `KANDEV_MOCK_PROVIDERS` remains
generic E2E infrastructure, but the Office-specific routable-provider catalogue
does not remain a production routing authority.
## Consequences
### Positive
- Kanban and Office share one routing model, runtime, settings surface, health
service, and observability contract.
- Office configuration becomes smaller: an agent chooses an execution agent
profile.
- One task session can survive provider changes without creating misleading
session tabs.
- The initial feature has no dependency on new plugin contracts.
- A failed account is suppressed across concurrent tasks without disabling
healthy accounts from the same provider.
### Costs
- The dynamic conductor becomes a stateful runtime boundary that must persist
route generation, active concrete profile, downstream session identity, and
circuit state.
- The UI needs explicit route-transition and capability-replacement events.
- Cross-provider continuation is semantic rather than lossless. It depends on
durable task and repository state plus a bounded resume prompt.
- Existing Office routing runtime ownership and settings UI must be removed
before Office is enabled. Existing table rows remain stored but inactive.
## Alternatives Considered
### Keep separate Kanban and Office routers
Rejected. Profile selection has different behavior in each workspace mode.
This alternative also duplicates routing policy, health state, UI, and tests.
### Keep routing in the workflow or Office scheduler
Rejected. A task-level scheduler must not manage downstream ACP sessions or
provider-native resume tokens. It also creates another task session or tab
when a provider changes.
### Treat the dynamic profile as an ordinary ACP provider
Rejected. No downstream ACP agent can import another provider's session, and
standard ACP does not describe Kandev's logical route identity or atomic
capability replacement. The conductor must own those extensions.
### Include plugin telemetry in the initial delivery
Rejected for this package. Provider-error fallback and ordered routing deliver
the dynamic-profile value without a new plugin contract. Telemetry needs a
separate contract, rollout flag, persistence model, and test package.
## Relationship to Prior Decisions
This decision supersedes
[Separate Office identity from routed execution profiles](2026-07-15-office-agent-execution-profile-routing.md)
as the owner and scope of routing. It retains that decision's separation
between Office identity and concrete execution configuration, then adds a
dynamic routing-policy identity between them.
This decision amends
[Separate Agent Error Evidence From Recovery Policy](2026-08-08-provider-neutral-agent-error-recovery.md):
the recovery policy is now selected by concrete versus dynamic profile kind,
not by Kanban versus Office workspace mode.
This decision also amends
[Attribute Runtime Configuration to Turns](2026-07-18-turn-configuration-snapshots.md):
a routed turn's immutable configuration snapshot includes its concrete
execution profile and route generation.
Product behavior is specified in
[Dynamic Agent Routing](../specs/agents/dynamic-agent-routing.md). Deferred
plugin behavior is specified separately in
[Dynamic Agent Telemetry Routing](../specs/agents/dynamic-agent-telemetry-routing.md).