# Host Agent Local Console Task Submission The Host Agent local Console adds a **Submit task to current Host** form on the authenticated Tasks page. The form lets an operator who is already signed in to the loopback Console enqueue a goal task for the current Host without going through the Cloud Console or the public SDK. The submission reuses the existing Host self-submission contract (a single authenticated `POST /internal/v1/hosts/{host_id}/tasks` against the control plane). It does not introduce a new Cloud endpoint, a public task scope, or a cross-Host target. ## Automatic vs explicit device The form offers two submission modes for the device target: - **Automatic** — the form omits the `device_id` field. The Cloud scheduler picks any eligible device currently registered for the authenticated Host. The Console labels this option *Automatic (let Host choose an eligible device)*. - **Explicit device** — the operator selects one of the device IDs that are currently registered in the running `DeviceManager`. In enrollment-managed deployments these are the Cloud device IDs assigned by the Host enrollment flow (the same IDs the heartbeat sends to the control plane), not the local configuration IDs. The Console revalidates the selected device against a fresh `DeviceManager.list_devices()` snapshot before issuing the outbound request. If the device was removed between page render and POST, the Console returns a validation error and never calls the Host client. ## Required state and policy - The Console session must be valid (the same local account login used for the rest of the Console). The submission form is rendered only for authenticated sessions, and the POST handler requires both a valid session and the matching CSRF token. - The Host must have completed self-enrollment. The Host self-submission client is wired in by `host_agent.app.create_application` from the same asynchronous `HostAgentClient` instance that drives heartbeat, claim, lease renewal, and result reporting, so the submission form disappears with a clear *submission client is not available yet* message until enrollment is complete. - The Cloud control plane must have `self_submission_enabled = true` in the Host governance policy. A disabled policy is returned to the operator as a definitive 4xx rejection ("Host self-submission is disabled") without recording a successful audit entry; the cached policy is *not* used for local authorization, so a stale cache never silently denies a newly enabled Host. ## Cloud queue semantics - Task creation is **not** idempotent. After the POST reaches the control plane, the Cloud scheduler assigns a task ID. The Console never retries the creation call. Even when the response is lost, a 5xx is returned, or the success payload is malformed, the Console only reports the outcome as **unknown** and instructs the operator to check the Cloud console before submitting again. This avoids the known failure mode where one operator click duplicates a task in the Cloud queue. - The local Console does not show queue progress for a freshly submitted task. After a successful submission the form redirect carries the Cloud task ID in the URL and the Tasks page shows *Task submitted. Cloud task ID: \*; execution progress, lease state, and the terminal result continue to appear on the local Tasks page only after the Cloud scheduler assigns the task to this Host and the assignment is processed locally. ## Outcome-unknown procedure The Console intentionally distinguishes the **unknown outcome** case from both a confirmed submission and a definitive rejection: - A transport failure (DNS, connect, read timeout, dropped connection), any 5xx response, or a malformed 2xx payload makes the local client raise `HostTaskSubmissionUnknownError`. The Console catches it and redirects the operator to `GET /tasks?outcome=unknown`, which renders a *Submission outcome is unknown. The task may have been queued. Check the Cloud console before submitting again.* notice. No history entry is written and no success confirmation is shown. - A definitive 4xx rejection (for example, a disabled self-submission policy or a target device that the control plane does not recognize as owned by this Host) raises `HostAgentAPIError`. The Console renders the Cloud's `detail` message safely through Jinja autoescape without echoing the goal text or any credentials. No history entry is written and no task ID is shown. ## Auditing - Confirmed submissions write a `task_submission` history row that records the Cloud task ID and, when applicable, the target device ID. The row deliberately does **not** persist the goal, the Host token, the session cookie, the lease secret, or any other operator credential. The row is bounded by the existing `console_history_limit` and survives process restarts. - A local audit write failure is best-effort: it does not turn a Cloud-confirmed submission into a retryable failure. A `task_submission` history row is only written after the Cloud returns a 2xx with a usable task ID, so a rejection or unknown outcome can never produce a misleading successful audit entry. ## Operator checklist 1. Sign in to the loopback Console with a valid local account. 2. Open **Tasks**. 3. Type a non-empty goal in the textarea. 4. Choose **Automatic** to let the Cloud pick a device, or pick a listed device ID for an explicit target. 5. Click **Submit task**. 6. On confirmation, the page shows *Task submitted. Cloud task ID: \*. Track the task from the Cloud console; the local Tasks list will fill in once the scheduler assigns the task to this Host. 7. If the page shows the *outcome is unknown* notice, check the Cloud console for a matching task before submitting again. 8. If the page shows a Cloud rejection, fix the underlying issue (policy, device ownership, lease state) and submit again. The page does not preserve the goal text in the URL, in the error message, or in the history; retype the goal when retrying.