Console Agent creation
Overview
Trace the Create Agent form from plugin, model, and repository choices to a provisioning request or an ordinary draft, then Secret grants and authentication. The parent flow authorizes the page first; the creation reference owns user-visible behavior.
Entry Points
apps/controller/src/console/agents/create.mts:renderCreateAgentrenders the form (renderAgentForm) and submits it.apps/controller/src/console/agents/plugin-fields.mjs:createPluginFieldsandagents/repositories.mts:createRepositoryFieldssupply the plugin and repository fields.- A signed-in caller with Namespace Agent
create; each write is authorized again.
Flow
graph TD
A["Create Agent form"] --> B["Plugins, model, and repository choices"]
B -->|repository access required and missing| H["Explain the requirement before provisioning"]
B -->|supported Dedicated| C["Queue provisioning with inline Configuration"]
C --> D["Worker creates resources, grants and first deployment"]
B -->|ordinary draft| E["Validate, then POST Configuration and Agent"]
E --> F["Grant Agent use of selected Secrets"]
F -->|failed| G["Retry credential access"]Execution Trace
1. Edit plugin selections and discover plugins
agents/plugin-fields.mjs:createPluginFields edits Agent plugins separately
from Configuration. Invalid JSON and untouched fields survive; clearing overrides
restores inheritance. Submission, uncertain outcomes, or invalid JSON lock editing.
The selected Harness's entry in capabilities.pluginCatalogs defines the policy controls.
A credential/access reminder sits beside the expandable
Access and credential setup disclosure. Instructions, list, and details
scroll within the dialog; search and pagination remain reachable.
While mounted, Available and Configured retain separate selected plugins, queries,
tool filters, and expanded rows without fetching pages or changing drafts.
Removed selections clear their details; discovery resets clear both tabs.
agents/plugin-discovery.mjs:createPluginDiscovery (loadCatalog, loadTools) implements
PAT discovery: the selected or
Preset Secret takes precedence over an entered token. OCC reads the
Secret server-side. Pagination is upstream; filtering is local. Selecting a plugin loads tools.
Credential, provider, and Harness changes clear results and invalidate pending reads.
The model credential, people search and Slack token fields use the shared
Secret picker. When a field first opens,
renderCreateAgent reads GET /namespaces/:namespaceId/permissions/secrets: the readable
Secrets the caller may not operate, reported only to a caller with Namespace read and Agent
create. Agent create, Configuration create and the people search refuse such a Secret, so each
field lists it disabled with its reason and never stages it; the Preset's Use existing Secret
list does the same, and Use Preset refuses it. A permission reread, a refused create or an
inline Secret create reads the answer again. A failed read keeps the last answer, or marks
none before any answer.
2. Choose a model and starter Configuration
create.mts:MODEL_CHOICES supplies unauthenticated static lists and manual entry.
renderAgentForm toggles manualModel, invoking updateModelConfiguration and
updateControls. Manual mode preserves model.value and clears the dropdown.
resetModelChoices restores the current provider's list, including Configuration
JSON changes, retaining only listed models; otherwise both inputs clear.
Provider and authentication changes normally reset the model. updateControls
sets the button label, visibility, and required input; the toggle moves focus.
Returning preserves credentials and unrelated Configuration. Saved Configuration
and pending requests lock the toggle.
If a change needs to update plugins.allow for Codex and the field is malformed,
the form keeps the JSON as typed and focuses Configuration JSON. Use an
array of strings or remove the field. A refused model selection, provider change,
or Harness change keeps the last applied model, provider, Harness, and execution
mode. The authentication method can still change: the form clears the prior
credential and shows the new method's fields without changing the model or JSON.
Before merging a model selection, canMergeModelCatalog checks the provider list
that the selection would update. If the list is malformed, the form keeps the
draft JSON as typed, opens Advanced settings, and focuses Configuration JSON.
The warning names the affected models.providers.<provider>.models field. The
edit sends no write request. Correct the list and retry
to apply the model.
Before a model change rewrites the JSON, readModelConfiguration rejects values
that parse as infinity, including nested numbers such as 1e400. Serialization
would otherwise replace them with null. The form skips the rewrite, keeps the
raw draft, and focuses Configuration JSON with an inline error. The same
number check stops Create Agent before any writes. Replace the value with a
number closer to zero and retry the action.
Submission still matches the primary Configuration model to model.value and
uses the normal server validation.
configurationTemplate enables Control UI with loopback origins on port 18789.
Compute supplies gateway authentication; Presets replace the starter unchanged.
Native admin access owns HTTPS isolation.
3. Load repository options
agents/repositories.mts:createRepositoryFields loads GET /namespaces/:namespaceId/agents/repository-options,
which authorizes Agent create. Create Agent holds this read until the permission answer allows
Agent create, or the Installation read succeeds without an answer; after a known refusal it
reads none and says so, without a retry. After the read it requests optional descriptions for visible refs. Repository admission resolves the
submitted repositoryAccess. Retained drafts keep selections through failed
discovery; retries recheck current policy. Only 503 REPOSITORY_OPTIONS_UNAVAILABLE
or meta.notConfigured (no repository settings) permits creation without bindings, and only without retained selections; other
failures block submission.
The credential catalog includes ordinary Gateway sources even when a Git Proxy is selected. Repository access remains optional in the form. An ordinary Gateway source without protected sources or repository bindings needs no Proxy admission. Source-free deployment follows the protected admission flow.
4. Submit provisioning or an ordinary draft
Supported Dedicated runtimes submit inline Configuration, optional repository
access, and Secret references to provisioning, even after
a 503 REPOSITORY_OPTIONS_UNAVAILABLE outage. Before it saves a model Secret or registers a
Credential Source for the request, the form runs the preflight below with the planned method.
The worker reauthorizes, creates resources and exact Secret grants, and deploys; Console polls and opens the saved version. Where the form cannot follow a placement
refusal's advice to select Embedded OpenClaw, it shows only the missing setting.
The Installation capabilities do not show whether the Compute Driver supplies a Preset's
operator-managed (runtime) binding. When the capabilities are read, checkRuntimeBinding sends
that binding to the preflight below; a harnessAuth.method refusal disables Create Agent and
names what an administrator changes, because the form can change neither the binding nor the mode.
Before any write, create.mts:renderAgentForm checks the Agent name with the API's Name rule
(namespaces.mts:namespaceNameProblem) and each initial workspace file with the Workspace tab's
agents/workspace.mts:workspaceFileProblem, which names the file; the check opens Advanced
settings to it. A new Secret gets no operate grant, so where the operate answer above denies
any readable Secret, the Preset's Create new Secret choice is disabled and a typed model key
is refused before any write. It then checks the Agent name, draft values with their staged channel
secretBindings, plugins and the planned harnessAuth
(only the method for a Secret it saves next or registers with a Credential Gateway; none for
ChatGPT sign-in) with
POST /namespaces/:namespaceId/agents/validate. After Agent create authorization,
OCC rejects Configuration Secret bindings that name another Namespace, then applies
Configuration create's size, content, and required Configuration Driver admission checks
to the unsaved Configuration. It next checks the Agent's plugins and topology, then refuses
a name another Agent has with Agent create's 409. A channel token reference without a
binding receives Configuration create's 400. Preflight writes nothing; Secret existence
and operate checks stay with the actual writes. list.mts:channelBindingRefusal shows
the channel refusal, and the same one from
provisioning, only under the Configuration JSON;
the Slack drawer requires both token Secrets while Slack is enabled. It then posts
{kind: "agent", values, secretBindings} to .../configurations and submits its ID,
plugins, initialWorkspaceFiles, and workspaceDefaultsId to .../agents.
Console checks the returned Secret name and Namespace, the Configuration values and bindings
it sent, and the Agent name, Namespace and Configuration. Before granting credential access it
reads that Agent and confirms its service principal matches the create response. A mismatch
stops the grant; Retry credential access reads the saved Agent again without recreating it.
An unrecognized success leaves the outcome unknown and blocks another create.
For an uncertain Credential Source registration, Check credential registration reads
the source for this request and confirms its type and Secret references before continuing.
After a known registration refusal, empty checks retain the refusal and the saved Secret.
They do not retry writes. A confirmed validation refusal unlocks source correction;
other known refusals keep the same registration for an explicit Create Agent retry.
If the Console times out waiting for validation, it stops before the save step,
keeps the form entries, and lets the caller select Create Agent to try again.
Success opens revision=draft.
OCC stages all four workspace textareas, including unchanged/empty values, outside
Agent/Configuration for workspace setup.
5. Grant Secret access and edit authentication
create.mts:grantConfigurationSecretAccess grants exact Secret operate for final
same-Namespace env bindings. Failure retains the Agent; Retry credential access
rereads grants without duplication. A session known not to administer the Installation sends
no IAM request and opens the saved draft: the member's
deploy writes these grants.
After a refused Installation read, Your access shows whether Agent create
is allowed, refused, or unchecked. When allowed,
create.mts:loadNamespaceCapabilities reads GET /namespaces/:namespaceId/agent-capabilities.
packages/occ/src/index.ts:getNamespaceAgentCapabilities checks
Agent create on the requested collection
before resolving that Namespace by ID. The API returns 403 on denial and 404
if an authorized request names a missing Namespace. The lookup needs no
Namespace read grant.
The form applies these capabilities as it applies the Installation's. The
Namespace response gives the form the credential source types to register a
model Secret with a Credential Gateway as in step 4. It omits
agentProvisioning, so the form saves a draft. When browser access is enabled,
the response includes nativeAdmin and the form offers Enable browser chat.
Agent browser access owns the later
access checks. A failed permission read adds Retry permission check, which
rereads only that answer. A retry that refuses create shows the refusal a fresh load shows,
without the form. Failed Agent writes retain Configuration ID
and lock JSON/Harness for explicit reuse. Writes never retry automatically; drafts
save no version and start no runtime.
agents/harness-auth.mjs edits bindings and shows
Secret identities,
never values. Authentication source uses the shared form-field presentation;
issued-account lookup feedback stays inside its conditionally visible account field.
Switching methods hides that feedback without changing the lookup or saved choice.
Debugging and Verification
tests/browser/console-agents.test.ts,console-agent-presets.test.ts, andconsole-agent-repositories.test.tsexercise the form against the real controller.- A refused write shows the API's reason and a request ID; inspect saved Configuration and Agent IDs before retrying.
