diff --git a/frontend/src/components/settings/GpuAgentCard.vue b/frontend/src/components/settings/GpuAgentCard.vue
new file mode 100644
index 0000000..d4a3d96
--- /dev/null
+++ b/frontend/src/components/settings/GpuAgentCard.vue
@@ -0,0 +1,166 @@
+
+
+
+ The agent is a container you run on the machine with the GPU. It
+ authenticates with the token below, leases jobs from this server, computes
+ on the GPU, and posts results back — all over HTTP. Start it when you want
+ a burst; stop it to reclaim the card.
+
+
+
+ Agent token
+ Loading…
+
+
+ {{ masked ? maskedToken : tokenValue }}
+
+
+ Rotate
+
+
+ Generate token
+
+
+ Point the agent at {{ baseUrl }} with this token. Rotating
+ invalidates the old token — update the agent after you rotate.
+
+
+
+
+ Work queue
+
+
{{ queue.pending }}
pending
+
{{ queue.leased }}
in flight
+
+
+
+
+ Queue character embedding (CCIP)
+
+ Enqueues every image that doesn't have a CCIP embedding yet. Nothing
+ processes until the agent is running.
+
+
+
+
+
+
+
diff --git a/frontend/src/components/settings/MaintenancePanel.vue b/frontend/src/components/settings/MaintenancePanel.vue
index 44dafe6..77a8035 100644
--- a/frontend/src/components/settings/MaintenancePanel.vue
+++ b/frontend/src/components/settings/MaintenancePanel.vue
@@ -27,6 +27,7 @@
+
@@ -54,6 +55,7 @@ import MissingFileRepairCard from './MissingFileRepairCard.vue'
import DbMaintenanceCard from './DbMaintenanceCard.vue'
import MLThresholdSliders from './MLThresholdSliders.vue'
import HeadsCard from './HeadsCard.vue'
+import GpuAgentCard from './GpuAgentCard.vue'
import AllowlistTable from './AllowlistTable.vue'
import AliasTable from './AliasTable.vue'
import TagEvalCard from './TagEvalCard.vue'
diff --git a/frontend/src/stores/gpu.js b/frontend/src/stores/gpu.js
new file mode 100644
index 0000000..6c677c0
--- /dev/null
+++ b/frontend/src/stores/gpu.js
@@ -0,0 +1,33 @@
+import { defineStore } from 'pinia'
+
+import { useApi } from '../composables/useApi.js'
+
+// GPU agent control surface (#114): the FC-side admin for the desktop agent —
+// the bearer token it authenticates with, the job-queue depth, and the backfill
+// trigger. The agent itself talks to /api/gpu/jobs/* over HTTP; nothing here
+// touches Redis/Postgres directly.
+export const useGpuStore = defineStore('gpu', () => {
+ const api = useApi()
+
+ // { token:
, configured: bool }
+ async function token() {
+ return await api.get('/api/gpu/token')
+ }
+
+ // Generate a fresh token (invalidates the old one). Returns { token }.
+ async function rotateToken() {
+ return await api.post('/api/gpu/token/rotate')
+ }
+
+ // { pending, leased, done, error }
+ async function status() {
+ return await api.get('/api/gpu/status')
+ }
+
+ // Enqueue a job per image lacking one for `task` (the agent drains it).
+ async function backfill(task = 'ccip') {
+ return await api.post('/api/gpu/backfill', { body: { task } })
+ }
+
+ return { token, rotateToken, status, backfill }
+})