fleetTasks — NEW read-observe wire verb (ai/services/fleet/fleetWireMethods.mjs, browser twin apps/agentos/config/fleetWireMethods.mjs, FleetControlBridge.fleetTasks) |
this ticket; fleetServerPolicy.mjs ledgers (awaiting-s3 / read-observe) |
params reserved ({} today); returns the envelope below; registered on both wire lists and both S1 policy ledgers |
source not wired → {capability:{state:'unavailable', reason:'fleet tasks source not wired'}, viewer:null, sources:{}, running:[], queued:[], recent:[], counts:{0,0,0}} |
verb JSDoc |
dispatchFleetRequest.spec allowlist, fleetServer.spec ledgers, ai:lint-fleet-vocabulary-parity |
| Identity binding |
RequestContextService.getAgentIdentityNodeId() through the injected resolveViewerIdentity, resolved INSIDE every readTasks() call (the memories-source posture) |
the envelope stamps the server-resolved viewer; every producer operation receives {} — no caller identity claim ever rides the operation |
non-canonical viewer → throws authenticated ingress did not bind a canonical viewer identity → dispatcher operationFailed → cockpit fallback fleet tasks read failed (never a defaulted identity) |
source JSDoc |
fleetTasksSource.spec "refused, never defaulted" |
Envelope {capability:{state, capturedAt, reason?}, viewer, sources:{deployment, rem, ingestion}, running[], queued[], recent[], counts} |
this ticket |
fold: wired = every WIRED axis answered (wired, stale or degraded — the reader's retained-snapshot statuses still measure, matching ANSWERED_STATES in the source); partial = some answered; unavailable = none (reason:'no-task-source-answered'); an unwired axis never degrades the fold |
each sources.<axis> carries {state, reason, detail?} (+ observedAt for deployment, scope for ingestion) |
source JSDoc |
fleetTasksSource.spec fold cases (wired / unwired-does-not-degrade / partial / unavailable) |
Row {id, section, name, source, state, at, progress: {kind:'determinate'|'backlog', done, total}|null, detail} |
this ticket; AgentOS.model.FleetTask converts (unknown section/source → null) |
running + recent newest-first, queued soonest-first, instant-less rows last; 12 rows per section; progress only when both counts are real and total > 0 |
— |
model + source JSDoc |
source ordering/cap specs, tasksPane.spec |
Producer — deployment snapshot: get_deployment_state_snapshot → readDeploymentStateSnapshot() (ai/services/memory-core/helpers/deploymentStateBridgeStore.mjs) |
that reader's envelope contract: {ok:true, status:'available', snapshot} · {ok:false, status:'stale', snapshot, reason:'snapshot-stale'} · {ok:false, status:'degraded', snapshot, reason} (both RETAIN the snapshot) · {ok:false, status:'unavailable', snapshot:null, reason} |
available → axis wired, rows from tenantRepoSync.task (running / last completion), tenantRepoSync.repos[] (one queued row per enabled repo with nextDueAt; due → word due; corpusOutstanding → backlog gauge), maintenance.retry (queued under its phase), recoveryRuns.entries[] (in flight → running, finished → recent), selfHeal.summary.currentlyFrozen[] (running, word frozen); stale → axis stale, rows retained; tenant/repo identifiers never leave the reducer (identity-hash labels only) |
other ok:false → axis unavailable with the producer reason; thrown read → deployment-read-failed + redacted detail; completion time: completed → lastSuccessAt ?? lastRunAt, failed → lastErrorAt ?? lastRunAt, skipped → lastRunAt |
source JSDoc |
live-shaped fixture; the REAL stale shape; failed + skipped completion witnesses; tenant-identifier leak witness |
Producer — REM: get_rem_pipeline_state → HealthService.buildRemPipelineState() (ai/services/memory-core/HealthService.mjs:859-900, OpenAPI openapi.yaml) |
{undigested, digested, sessionNodes, topologyConflicts, recentCycles[{runId, wallClockMs, cycleOverflowSignal, cycleOverflowRatio, outcome}], axisErrors?} — cycles carry NO instant and no active flag; a failed axis read records axisErrors.<axis> and falls back to 0 |
exactly ONE queued row mc:rem:digest, word backlog, gauge digested / (digested + undigested), at: null; the latest cycle's outcome rides the detail; never a running claim |
axisErrors.undigested or .digested → axis unavailable (rem-axis-error) — fallback zeroes never become a wired measurement; unrecognized payload → rem-payload-unrecognized |
source JSDoc |
producer-shaped fixtures; axisErrors red control |
Producer — KB ingestion: get_ingestion_progress → IngestionService.getIngestionProgress() (ai/services/knowledge-base/IngestionService.mjs:831, observedScope: this-process-only) |
that producer's {status, active, phase, stalled, startedAt, completedAt, totalChunks, embeddedChunks, errorCount, lastRunSummary, observedScope} |
bound in IN-PROCESS mode only (the plane client proves identity against the Memory Core plane and holds no Knowledge Base session); active → running row, determinate embeddedChunks/totalChunks, stalled → word stalled; idle → recent row from lastRunSummary; tenantId / repoSlug never rendered |
plane mode → axis unwired (ingestion-verb-unreachable-from-this-process), no degrade; thrown → ingestion-read-failed; unrecognized → ingestion-payload-unrecognized |
source + wiring JSDoc |
source spec (active / stalled / idle / leak) |
| Bounds + redaction |
redactReadFailure / redactCredentials (the fleet's single redaction authority); MAX_ROWS = 12 |
the ~100 KB snapshot never crosses the wire; every thrown-read detail passes the redactor before it is bounded |
— |
source JSDoc |
"a throwing axis carries a redacted detail" witness |
Consumer — FleetCockpit.loadTasks (boot, liveness tick with its own in-flight cap, reconnect) → TasksPane (AgentOS.store.FleetTasks projection) |
the sibling loaders' three laws (#16415 write-time resolution) |
typed unavailable for an unwired/throwing bridge; generation fence; pane resolved at WRITE time through getTasksPane() (docked / vesseled / returning) |
transport-cold (no source axes) → the labeled sample stays (Activity-stream convention); source-cold → honest empty lines under the reason |
cockpit + pane JSDoc |
fleetCockpit.spec loadTasks block, tasksPane.spec, FleetTasksPaneNL journey through the authenticated loopback transport |
Tasks pane: the orchestrator schedule as a first-class cockpit surface
Context
Operator, live session 2026-08-18: "a very polished view for the orchestrator scheduler tasks (what is running, % done if possible, next scheduled tasks)." The cockpit's center answers WHO (FleetGrid: agents + state); nothing answers WHAT (work in flight, progress, queue).
The Problem
Mission control without the work axis. The operator reads "0 working · 9 unobserved" but cannot see the running ingestion, the REM backlog, or the next scheduled run. The fleet's memories became a proven surface in the
#17271L1 session; the schedule is the missing operational twin — WHO is only half of mission control.The Architectural Reality
ai/deploy/docker-compose.local-agent-os.yml(services: chroma, kb-server, mc-server, orchestrator, fleet-server, ingress).MemoriesPane/CatchUpPane(apps/agentos/view/fleet/) — snapshot-held, intent-firing, cockpit-owned authenticated bridge reads; the Tasks pane follows the same pane contract.The Fix
A Tasks pane in the south content-tab family (#17269's target model): running tasks (with progress where a truth verb reports it), queued/next-scheduled (with source), recently completed — provenance-labeled per source. Where the orchestrator-schedule verb does not exist yet, the pane renders the honestly-labeled sample state (spine-banner honesty discipline) — it never fakes liveness. The narrowest missing wire verb is named at implementation time as its own service ticket, after D#11857's decomposition settles what serves it.
Contract Ledger Matrix (T3 — added 2026-08-22 at PR #17542 review round 1, RA-3; the diff and its witnesses match this matrix exactly)
fleetTasks— NEW read-observe wire verb (ai/services/fleet/fleetWireMethods.mjs, browser twinapps/agentos/config/fleetWireMethods.mjs,FleetControlBridge.fleetTasks)fleetServerPolicy.mjsledgers (awaiting-s3/read-observe){}today); returns the envelope below; registered on both wire lists and both S1 policy ledgers{capability:{state:'unavailable', reason:'fleet tasks source not wired'}, viewer:null, sources:{}, running:[], queued:[], recent:[], counts:{0,0,0}}dispatchFleetRequest.specallowlist,fleetServer.specledgers,ai:lint-fleet-vocabulary-parityRequestContextService.getAgentIdentityNodeId()through the injectedresolveViewerIdentity, resolved INSIDE everyreadTasks()call (the memories-source posture)viewer; every producer operation receives{}— no caller identity claim ever rides the operationauthenticated ingress did not bind a canonical viewer identity→ dispatcheroperationFailed→ cockpit fallbackfleet tasks read failed(never a defaulted identity)fleetTasksSource.spec"refused, never defaulted"{capability:{state, capturedAt, reason?}, viewer, sources:{deployment, rem, ingestion}, running[], queued[], recent[], counts}wired= every WIRED axis answered (wired,staleordegraded— the reader's retained-snapshot statuses still measure, matchingANSWERED_STATESin the source);partial= some answered;unavailable= none (reason:'no-task-source-answered'); anunwiredaxis never degrades the foldsources.<axis>carries{state, reason, detail?}(+observedAtfor deployment,scopefor ingestion)fleetTasksSource.specfold cases (wired / unwired-does-not-degrade / partial / unavailable){id, section, name, source, state, at, progress: {kind:'determinate'|'backlog', done, total}|null, detail}AgentOS.model.FleetTaskconverts (unknown section/source → null)progressonly when both counts are real and total > 0tasksPane.specget_deployment_state_snapshot→readDeploymentStateSnapshot()(ai/services/memory-core/helpers/deploymentStateBridgeStore.mjs){ok:true, status:'available', snapshot}·{ok:false, status:'stale', snapshot, reason:'snapshot-stale'}·{ok:false, status:'degraded', snapshot, reason}(both RETAIN the snapshot) ·{ok:false, status:'unavailable', snapshot:null, reason}wired, rows fromtenantRepoSync.task(running / last completion),tenantRepoSync.repos[](one queued row per enabled repo withnextDueAt;due→ worddue;corpusOutstanding→ backlog gauge),maintenance.retry(queued under itsphase),recoveryRuns.entries[](in flight → running, finished → recent),selfHeal.summary.currentlyFrozen[](running, wordfrozen); stale → axisstale, rows retained; tenant/repo identifiers never leave the reducer (identity-hash labels only)ok:false→ axisunavailablewith the producerreason; thrown read →deployment-read-failed+ redacted detail; completion time:completed→lastSuccessAt ?? lastRunAt,failed→lastErrorAt ?? lastRunAt,skipped→lastRunAtget_rem_pipeline_state→HealthService.buildRemPipelineState()(ai/services/memory-core/HealthService.mjs:859-900, OpenAPIopenapi.yaml){undigested, digested, sessionNodes, topologyConflicts, recentCycles[{runId, wallClockMs, cycleOverflowSignal, cycleOverflowRatio, outcome}], axisErrors?}— cycles carry NO instant and no active flag; a failed axis read recordsaxisErrors.<axis>and falls back to 0mc:rem:digest, wordbacklog, gaugedigested / (digested + undigested),at: null; the latest cycle'soutcomerides the detail; never a running claimaxisErrors.undigestedor.digested→ axisunavailable(rem-axis-error) — fallback zeroes never become a wired measurement; unrecognized payload →rem-payload-unrecognizedaxisErrorsred controlget_ingestion_progress→IngestionService.getIngestionProgress()(ai/services/knowledge-base/IngestionService.mjs:831,observedScope: this-process-only){status, active, phase, stalled, startedAt, completedAt, totalChunks, embeddedChunks, errorCount, lastRunSummary, observedScope}embeddedChunks/totalChunks,stalled→ wordstalled; idle → recent row fromlastRunSummary;tenantId/repoSlugnever renderedunwired(ingestion-verb-unreachable-from-this-process), no degrade; thrown →ingestion-read-failed; unrecognized →ingestion-payload-unrecognizedredactReadFailure/redactCredentials(the fleet's single redaction authority);MAX_ROWS = 12FleetCockpit.loadTasks(boot, liveness tick with its own in-flight cap, reconnect) →TasksPane(AgentOS.store.FleetTasksprojection)getTasksPane()(docked / vesseled / returning)fleetCockpit.specloadTasks block,tasksPane.spec,FleetTasksPaneNLjourney through the authenticated loopback transportAcceptance Criteria
Out of Scope
The orchestrator-schedule wire verb (named follow-up once D#11857 settles the serving shape) · acting on tasks (cancel/re-run — operator actuation is its own security surface) · the other panes' content design (#17268).
Related
Epic #14560 (parent) · #17269 (south-tab placement) · D#11857 (scheduler substrate decomposition) · D#16990 (the missing third state) · #17271 (the L1 surface precedent).
Live latest-open sweep: latest 20 re-checked 2026-08-18T08:15Z, no equivalent; A2A herd window clean (one unrelated engine claim #17327).
Origin Session ID: ca3c67ac-a3d6-4e93-98e0-c5f7f65011ee
Retrieval Hint:
query_raw_memories("tasks pane orchestrator schedule progress running queued cockpit south tab")