Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com> Co-authored-by: Carlos Florencio <carlosmflorencio@gmail.com>
10 KiB
status, system, requirements
| status | system | requirements | |
|---|---|---|---|
| current | tasks |
|
Task Create Agent Compatibility Recovery System Design
Purpose and boundaries
The task-create dialog (apps/web/components/task-create-dialog*.ts and
.tsx) owns agent and executor selection for new tasks. This design covers how
the dialog derives an agent compatibility state, replaces an incompatible
selection, and presents each state on desktop and phone.
The design uses, and does not own, two adjacent contracts:
- The compatibility rule
isAgentConfiguredOnExecutorinapps/web/lib/agent-executor-compat.ts, which reads the executor profile'sremote_credentialsandremote_auth_secretsconfiguration. - The remote-auth catalog served by
GET /api/v1/remote-credentialsand cached byuseRemoteAuthSpecsinapps/web/hooks/domains/settings/use-remote-auth-specs.ts.
Requirement mapping
| Requirement | Design section |
|---|---|
REQ-TASKS-TASK-CREATE-AGENT-COMPATIBILITY-001 |
Data and contracts, Control flow, Presentation |
Components and responsibilities
apps/web/components/task-create-dialog-computed.ts:useExecutorProfileCompatkeeps filteringagentProfilesintocompatibleAgentProfilesand derivesagentCompatStatethrough a purecomputeAgentCompatStatehelper.noCompatibleAgentremains the derived boolean that gates submission.apps/web/components/task-create-dialog-autopick.ts:decideAgentProfileAutopickgains a replacement path for a selected profile that is no longer compatible.useAgentProfileAutopickEffectapplies it.apps/web/components/task-create-dialog-form-body.tsx:AgentColumnrenders the selector, the empty state, the unavailable note, or the incompatible note per state.apps/web/components/task-create-dialog-prop-builders.ts: forwards the state, the selected agent profile label, and the effective workflow name.apps/web/components/task-create-dialog-footer.tsx:computeDisabledReasonreturns a state-specific reason key, andresolveDisabledReasonfills the agent and executor names.apps/web/components/task-create-dialog-setup.ts: the guarded submit handler applies the compatibility block to form, keyboard shortcut, and programmatic submissions.- Locale catalogs
apps/web/src/locales/<locale>/task.jsonown the copy.
Data and contracts
export type AgentCompatState =
| "compatible"
| "selected-incompatible"
| "selected-unavailable"
| "none-compatible";
computeAgentCompatState derives the state from the selected executor profile,
compatibleAgentProfiles, the effective agent profile id (the user value or the
workflow override) and its store row, the workflow lock, and the dynamic
routing feature flag:
- No executor profile selected:
compatible. - No effective agent id:
none-compatiblewhencompatibleAgentProfilesis empty, otherwisecompatible. - The effective agent id is in
compatibleAgentProfiles:compatible. - The selected row exists but is not selectable (disabled, or a dynamic
profile while routing is off):
selected-unavailablewhen another compatible profile exists. A locked disabled profile, or an unavailable selection with no alternative, isnone-compatible. A locked disabled profile is a profile-disable outcome, not a credential problem. - The workflow locks the agent:
selected-incompatible, even whencompatibleAgentProfilesis empty, so the locked note names the workflow and agent instead of the generic empty state. compatibleAgentProfilesis empty:none-compatible.- Otherwise:
selected-incompatible.
noCompatibleAgent equals agentCompatState !== "compatible", so the footer
and submit gates keep their existing input.
DialogComputedValues adds agentCompatState and
selectedAgentProfileName: string | null (the label of the effective agent
profile). DialogFormBodyProps and CreateEditSelectorsProps add
agentCompatState, selectedAgentProfileName, and
effectiveWorkflowName: string | null.
The autopick decision keeps its shape and adds an optional replaces field:
{ kind: "pick"; source: "lastId" | "defId" | "first"; id: string; replaces?: string }
The replacement gate runs before the existing already-set skip. It yields a
pick with replaces set to the current id when all of these hold: the dialog is
open, an agent id is set, no workflow lock applies (workflowAgentProfileId is
empty and the workflow has no agent), the auth catalog is loaded, an executor
profile is selected, compatibleAgentProfiles is non-empty, and the current id
is absent from compatibleAgentProfiles. The candidate order is unchanged:
last-used, workspace default, first compatible.
Presentation
| State | Workflow lock | Agent column |
|---|---|---|
compatible |
any | Existing selector (agent-profile-selector). |
none-compatible |
any | agent-profile-empty-state with task:noCompatibleAgentProfilesFor and the credentials link. |
selected-incompatible |
no | Selector with the compatible options plus agent-profile-incompatible-note with task:agentNotConfiguredOnExecutor and the credentials link. This state is transient until the replacement applies. |
selected-incompatible |
yes | agent-profile-incompatible-note with task:workflowAgentNotConfiguredOnExecutor (workflow, agent, executor) and the credentials link. No selector. |
selected-unavailable |
no | Selector with the compatible options plus agent-profile-unavailable-note with task:selectedAgentProfileUnavailable. This state is transient until the replacement applies. |
The credentials link keeps its existing target
/settings/executors/<executor-profile-id>.
Footer reason keys:
none-compatible: existingREASON_NO_COMPATIBLE_AGENT(task:noCompatibleAgentProfileFor).selected-incompatible: newREASON_SELECTED_AGENT_INCOMPATIBLE(task:selectedAgentNotConfiguredFor, withagentandtarget).selected-unavailable: newREASON_SELECTED_AGENT_UNAVAILABLE(task:selectedAgentProfileUnavailable, withagent).
New task namespace keys, added in en, pt-pt, zh-cn, zh-hk, zh-tw,
and regenerated pseudo:
agentNotConfiguredOnExecutor: "{{agent}} is not configured on {{target}}."workflowAgentNotConfiguredOnExecutor: "The {{workflow}} workflow uses {{agent}}, which is not configured on {{target}}."selectedAgentNotConfiguredFor: "{{agent}} is not configured on {{target}}. Configure agent credentials in Settings → Executors."selectedAgentProfileFallback: capitalized fallback for a sentence start.selectedAgentProfileFallbackInline: lowercase fallback for the workflow sentence where the agent name is not at the start.selectedAgentProfileUnavailable: "{{agent}} is unavailable. Select a compatible agent profile."
Mobile composition
The dialog renders one component tree for both viewports. The agent and
executor selectors sit in a grid that collapses to one column below the sm
breakpoint, so the note renders under the selector at full dialog width and
wraps. No drawer, new surface, or touch control is added. The nearest shipped
mobile exemplar is the create-task dialog flow in
apps/web/e2e/tests/task/mobile-create-task-branch-policy.spec.ts, which
already drives the executor profile selector on a phone viewport.
Control flow
- The user picks an executor profile.
handleExecutorProfileChangesetsfs.executorProfileIdand queues the last-used executor. This handler is unchanged. useExecutorProfileCompatrecomputescompatibleAgentProfilesandagentCompatStatein the same render.useAgentProfileAutopickEffectevaluates the replacement gate. A pick is applied throughsetAgentProfileIdon the next microtask, the same deferral the existing autopick uses. The effect does not callsyncTaskCreateLastUsed, so the stored last-used agent stays untouched. The submit gate stays closed while an unavailable selection is visible.AgentColumnrenders per the presentation table, and the footer resolves the disabled reason.
When the workflow locks the agent, useWorkflowAgentProfileEffect keeps
setting the locked id, the replacement gate is skipped, and the locked note is
shown until the user changes the workflow or configures the credentials.
Failure and recovery
- Auth catalog not loaded:
compatibleAgentProfilesequals the selectable list and the state resolves tocompatible. No replacement runs. This matches the existing deferral in the autopick gate. - Catalog request failure: the cached specs are empty, every agent on a remote
executor is blocked, and the state is
none-compatible. The empty state and credentials link remain the recovery path. This behavior is unchanged. - Agent profiles transiently empty during a refresh:
compatibleAgentProfilesis empty, so the replacement gate does not fire and a valid selection is not cleared. The existingagentProfilesLoadingguard keeps the empty state hidden while profiles load. - An unlocked disabled or dynamic-off selection with a compatible alternative: the dialog shows the unavailable note, blocks submit, and waits for the autopick replacement. It does not show the no-compatible empty state.
Persistence
None. The replacement is dialog state only.
users.settings.task_create_last_used.agent_profile_id changes only through
the manual selection handler and successful task creation, as recorded in
ADR 0028
and ADR 0041.
Observability
The executor-compat:autopick debug logger records the replaces id and the
source of a replacement decision. The existing [executor-compat] per-agent
check logs (ok, reason) remain the diagnostic for why a profile is
incompatible.