-
Notifications
You must be signed in to change notification settings - Fork 0
C09.1: administer durable UAR team planning in The Boss #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| schema: spec-driven | ||
| created: 2026-09-28 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| # Design | ||
|
|
||
| The Boss verifies workspace IDs against its own workspace store, reads the selected UAR generation and sends requests through its existing authenticated sidecar connection. It parses the returned team documents and refuses a response for another workspace. The renderer receives a typed projection via three fixed IPC routes: snapshot, create, add task. No arbitrary URL or untrusted method is accepted from the renderer. | ||
|
|
||
| The UAR API is the sole writer of team planning state. Create resolves an immutable TeamDefinition and private deployment binding at their exact versions. Task submission supplies an expected team revision and an idempotent command ID. Task dependencies are checked by UAR; the UI only guides entry. Member and task state are displayed as returned, with no local inferred execution status. | ||
|
|
||
| The Teams page uses the current UAR workspace selector and established settings components. It distinguishes a missing team-capable definition, unavailable sidecar, unsupported operation, empty board and failed mutation. All text uses existing locale catalogs. | ||
|
|
||
| The cadence increment records UAR/Boss source refs, then completes code. At its boundary, build with `pnpm build:mac:arm64` using the clean pinned C09 UAR source and launch the resulting app. Exercise create/add/restart/read in the packaged app. Build success alone does not establish durable function. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # C09.1 packaged Mac delivery | ||
|
|
||
| The complete local Mac ARM64 gate passed on 2026-09-28. The exact build, source fingerprints, sidecar archive, DMG checksum/signing state, and packaged-app operation are recorded in `librefang/docs/plans/agent-fabric-convergence/openspec/changes/afc-c09-bounded-teams-and-shared-task-board/c09-1-evidence.md` in the convergence initiative. The cadence run is `adcf4ebd-9fa7-4880-99ae-7545b6bd6bdf`, iteration `8d655a16-c393-44c7-9060-bbe69af76d06`. | ||
|
|
||
| The packaged application installed the immutable starter team and private workspace binding, created supervisor/worker, bounded map/reduce and independent peer planning graphs, recovered them after a managed UAR restart, confirmed isolation of a second workspace, and displayed a recovered board in Teams settings. The local DMG is `dist/The-Boss-2.2.6-mac-arm64.dmg`, SHA-256 `10c2c652fa15fe16d56bfe3cc6b3a494b7aa645f9c3bb4a4033d75e23b7f434e`. | ||
|
|
||
| This is planning-only functionality. Team execution and effect authority remain out of scope for C09.1. No Windows installer or public release is claimed by this receipt. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # UAR team planning in The Boss | ||
|
|
||
| ## Why | ||
|
|
||
| UAR has immutable TeamDefinition documents but no durable team instance or task board visible in The Boss. A customer cannot create or inspect a team plan through the installed application. | ||
|
|
||
| ## What changes | ||
|
|
||
| Expose UAR's C09.1 planning API through The Boss's trusted main-process adapter and typed IPC. Add a Teams page within UAR settings to select an installed immutable definition, create a workspace-scoped team instance, add tasks with dependencies and input/output contracts, and inspect the saved board after restart. Package the exact local UAR source in the Mac ARM64 application. | ||
|
|
||
| This increment is planning only. Claiming, delegation, agent turns, budgets and aggregate authority remain C09.2–C09.3 work. The Boss never becomes the board's persistence authority. | ||
|
|
||
| ## Owners and boundary | ||
|
|
||
| UAR worktree `/Users/gqadonis/Projects/prometheus/worktrees/agent-fabric-c09-uar` owns the API and durable state under its linked product change. The Boss runtime owner owns `src/main/ai/runtime/uar`, `src/shared/types`, `src/shared/ipc/schemas/prometheus.ts`, `src/main/ipc/handlers/prometheus.ts`, and `build/local-uar-source.json`. The Boss renderer owner owns `UarAdministrationWorkspace.tsx`, a new `UarTeamsPanel.tsx`, settings search, and every locale catalog. Build and functional operation happen once after production wiring. | ||
|
|
||
| ## Outcome | ||
|
|
||
| An installed Mac application can create a team plan from an exact installed definition, add two dependent tasks, restart UAR and the app, and read the same isolated workspace board. The build receipt identifies the packaged UAR revision and application artifact. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # Tasks | ||
|
|
||
| - [x] 1. UAR exposes a workspace-scoped, durable planning API for exact team definitions, instances, members and task DAG records; pin its source revision and native payload. | ||
| - [x] 2. The Boss adds fixed authenticated main-process routes and typed IPC for list/create/add with workspace scope and capability checks. | ||
| - [x] 3. The Boss adds an accessible Teams settings page with actionable creation and task planning, all locale strings and clear saved/error states. | ||
| - [x] 4. Build the complete Mac ARM64 application, run the installed planning workflow including restart and two-workspace isolation, then record exact source/artifact evidence at the cadence boundary. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,276 @@ | ||
| import { randomUUID } from 'node:crypto' | ||
| import { mkdtempSync } from 'node:fs' | ||
| import os from 'node:os' | ||
| import path from 'node:path' | ||
| import { setTimeout as delay } from 'node:timers/promises' | ||
|
|
||
| function requestExpression(route, input) { | ||
| return `(async () => { | ||
| const result = await window.api.ipcApi.request(${JSON.stringify(route)}, ${JSON.stringify(input)}); | ||
| return result.ok ? { ok: true, data: result.data } : { ok: false, error: result.error }; | ||
| })()` | ||
| } | ||
|
|
||
| async function request(evaluate, route, input) { | ||
| const result = await evaluate(requestExpression(route, input)) | ||
| if (!result?.ok) throw new Error(`${route}: ${JSON.stringify(result?.error ?? 'no response')}`) | ||
| return result.data | ||
| } | ||
|
|
||
| async function waitFor(evaluate, signal, expression, description, timeoutMs = 30_000) { | ||
| const deadline = Date.now() + timeoutMs | ||
| while (Date.now() < deadline) { | ||
| signal.throwIfAborted() | ||
| if (await evaluate(expression)) return | ||
| await delay(200, undefined, { signal }) | ||
| } | ||
| throw new Error(`${description} did not become available in The Boss`) | ||
| } | ||
|
|
||
| export default async function run({ evaluate, signal }) { | ||
| signal.throwIfAborted() | ||
| const directory = mkdtempSync(path.join(os.tmpdir(), 'boss-c09-team-')) | ||
| const workspaceResult = await evaluate(`(async () => window.api.dataApi.request({ | ||
| id: ${JSON.stringify(randomUUID())}, method: 'POST', path: '/agent-workspaces', | ||
| body: { path: ${JSON.stringify(directory)}, name: 'Cadence team planning' } | ||
| }))()`) | ||
| if (workspaceResult?.error || !workspaceResult?.data?.id) { | ||
| throw new Error(`Could not create a Boss workspace: ${JSON.stringify(workspaceResult?.error ?? 'empty response')}`) | ||
| } | ||
| const workspaceId = workspaceResult.data.id | ||
| const binding = await request(evaluate, 'prometheus.uar.teams.setup_starter', { workspaceId }) | ||
| if (binding.activationSupported) throw new Error('Starter team unexpectedly advertises execution support') | ||
| const snapshot = await request(evaluate, 'prometheus.uar.teams.snapshot', { workspaceId }) | ||
| if (!snapshot.capabilities.planning) throw new Error('UAR did not advertise durable team planning') | ||
| const definition = snapshot.definitions.find((item) => item.package.digest === binding.package.digest) | ||
| if (!definition) throw new Error('Installed starter team definition is missing from the catalog') | ||
| const team = await request(evaluate, 'prometheus.uar.teams.create', { | ||
| workspaceId, | ||
| commandId: randomUUID(), | ||
| deploymentBindingId: binding.id, | ||
| teamDefinition: { id: definition.id, version: definition.version, digest: definition.digest }, | ||
| input: { brief: 'Prepare a local delivery plan' } | ||
| }) | ||
| if (team.status !== 'inactive' || team.members.length !== 2) { | ||
| throw new Error('UAR did not materialize the two-member planning team') | ||
| } | ||
| const firstId = randomUUID() | ||
| const first = await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: team.id, | ||
| commandId: randomUUID(), | ||
| taskId: firstId, | ||
| expectedTeamRevision: team.revision, | ||
| title: 'Draft the implementation plan', | ||
| role: 'coordinator', | ||
| input: { brief: 'Outline the delivery' }, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [] | ||
| }) | ||
| const secondId = randomUUID() | ||
| const second = await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: team.id, | ||
| commandId: randomUUID(), | ||
| taskId: secondId, | ||
| expectedTeamRevision: first.revision, | ||
| title: 'Prepare the implementation', | ||
| role: 'worker', | ||
| input: { brief: 'Use the approved plan' }, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [firstId] | ||
| }) | ||
| const leftId = randomUUID() | ||
| const left = await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: team.id, | ||
| commandId: randomUUID(), | ||
| taskId: leftId, | ||
| expectedTeamRevision: second.revision, | ||
| title: 'Map part A', | ||
| role: 'worker', | ||
| input: { partition: 'A' }, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [] | ||
| }) | ||
| const rightId = randomUUID() | ||
| const right = await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: team.id, | ||
| commandId: randomUUID(), | ||
| taskId: rightId, | ||
| expectedTeamRevision: left.revision, | ||
| title: 'Map part B', | ||
| role: 'worker', | ||
| input: { partition: 'B' }, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [] | ||
| }) | ||
| const reduceId = randomUUID() | ||
| await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: team.id, | ||
| commandId: randomUUID(), | ||
| taskId: reduceId, | ||
| expectedTeamRevision: right.revision, | ||
| title: 'Reduce mapped parts', | ||
| role: 'coordinator', | ||
| input: { combine: ['A', 'B'] }, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [leftId, rightId] | ||
| }) | ||
| const peer = await request(evaluate, 'prometheus.uar.teams.create', { | ||
| workspaceId, | ||
| commandId: randomUUID(), | ||
| deploymentBindingId: binding.id, | ||
| teamDefinition: { id: definition.id, version: definition.version, digest: definition.digest }, | ||
| input: { brief: 'Record independent peer work' } | ||
| }) | ||
| const peerLeft = await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: peer.id, | ||
| commandId: randomUUID(), | ||
| taskId: randomUUID(), | ||
| expectedTeamRevision: peer.revision, | ||
| title: 'Peer research', | ||
| role: 'coordinator', | ||
| input: {}, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [] | ||
| }) | ||
| await request(evaluate, 'prometheus.uar.teams.add_task', { | ||
| workspaceId, | ||
| teamInstanceId: peer.id, | ||
| commandId: randomUUID(), | ||
| taskId: randomUUID(), | ||
| expectedTeamRevision: peerLeft.revision, | ||
| title: 'Peer design', | ||
| role: 'worker', | ||
| input: {}, | ||
| outputContract: { type: 'object' }, | ||
| dependsOn: [] | ||
| }) | ||
| const saved = await request(evaluate, 'prometheus.uar.teams.snapshot', { workspaceId }) | ||
| const persisted = saved.instances.find((item) => item.id === team.id) | ||
| const savedPeer = saved.instances.find((item) => item.id === peer.id) | ||
| if ( | ||
| persisted?.tasks.length !== 5 || | ||
| !persisted.tasks[1].dependsOn.includes(firstId) || | ||
| !persisted.tasks[4].dependsOn.includes(leftId) || | ||
| !persisted.tasks[4].dependsOn.includes(rightId) || | ||
| savedPeer?.tasks.length !== 2 || | ||
| savedPeer.tasks.some((item) => item.dependsOn.length) | ||
| ) { | ||
| throw new Error('The saved boards lost supervisor, map/reduce, or peer task dependencies') | ||
| } | ||
| const secondDirectory = mkdtempSync(path.join(os.tmpdir(), 'boss-c09-isolated-')) | ||
| const secondWorkspaceResult = await evaluate(`(async () => window.api.dataApi.request({ | ||
| id: ${JSON.stringify(randomUUID())}, method: 'POST', path: '/agent-workspaces', | ||
| body: { path: ${JSON.stringify(secondDirectory)}, name: 'Cadence isolated team workspace' } | ||
| }))()`) | ||
| if (secondWorkspaceResult?.error || !secondWorkspaceResult?.data?.id) { | ||
| throw new Error('Could not create a second Boss workspace') | ||
| } | ||
| const otherWorkspaceId = secondWorkspaceResult.data.id | ||
| const beforeRestart = await request(evaluate, 'prometheus.uar.teams.snapshot', { workspaceId: otherWorkspaceId }) | ||
| if (beforeRestart.instances.some((item) => item.id === team.id)) { | ||
| throw new Error('The first team leaked into another workspace') | ||
| } | ||
| const restart = await request(evaluate, 'prometheus.integration.start', { action: 'uar-restart' }) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This operation restarts only AGENTS.md reference: AGENTS.md:L57-L58 Useful? React with 👍 / 👎. |
||
| const deadline = Date.now() + 90_000 | ||
| let restartStatus | ||
| while (Date.now() < deadline) { | ||
| signal.throwIfAborted() | ||
| const state = await request(evaluate, 'prometheus.integration.snapshot', {}) | ||
| restartStatus = state.operations.find((item) => item.id === restart.id)?.status | ||
| if (restartStatus === 'succeeded') break | ||
| if (['failed', 'cancelled', 'interrupted'].includes(restartStatus)) { | ||
| throw new Error(`UAR restart finished with status ${restartStatus}`) | ||
| } | ||
| await delay(250, undefined, { signal }) | ||
| } | ||
| if (restartStatus !== 'succeeded') throw new Error('UAR restart did not finish within the delivery window') | ||
| const recovered = await request(evaluate, 'prometheus.uar.teams.snapshot', { workspaceId }) | ||
| const restoredTeam = recovered.instances.find((item) => item.id === team.id) | ||
| const restoredPeer = recovered.instances.find((item) => item.id === peer.id) | ||
| if ( | ||
| restoredTeam?.tasks.length !== 5 || | ||
| !restoredTeam.tasks[1].dependsOn.includes(firstId) || | ||
| !restoredTeam.tasks[4].dependsOn.includes(leftId) || | ||
| !restoredTeam.tasks[4].dependsOn.includes(rightId) || | ||
| restoredPeer?.tasks.length !== 2 | ||
| ) { | ||
| throw new Error('Team planning state did not survive UAR restart') | ||
| } | ||
| const isolated = await request(evaluate, 'prometheus.uar.teams.snapshot', { workspaceId: otherWorkspaceId }) | ||
| if (isolated.instances.some((item) => item.id === team.id)) { | ||
| throw new Error('Team planning scope changed after UAR restart') | ||
| } | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `(() => { | ||
| const button = [...document.querySelectorAll('button')].find((item) => item.innerText.trim() === 'Set up later'); | ||
| if (!button) return false; button.click(); return true; | ||
| })()`, | ||
| 'Skip onboarding action' | ||
| ) | ||
| await waitFor(evaluate, signal, `Boolean(document.querySelector('#app-sidebar'))`, 'Main app sidebar') | ||
| await request(evaluate, 'navigation.open_route_in_main', { path: '/settings/uar?panel=teams' }) | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `(() => { | ||
| const button = document.querySelector('button[aria-label="Workspace"]'); | ||
| if (!button) return false; button.click(); return true; | ||
| })()`, | ||
| 'UAR workspace selector' | ||
| ) | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `(() => { | ||
| const option = [...document.querySelectorAll('[role="option"]')].find((item) => | ||
| item.innerText.includes('Cadence team planning')); | ||
| if (!option) return false; option.click(); return true; | ||
| })()`, | ||
| 'Cadence workspace option' | ||
| ) | ||
| await waitFor(evaluate, signal, `document.body.innerText.includes('Team instances')`, 'UAR Teams settings') | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `Boolean(document.querySelector('button[aria-label="Choose a team instance"]'))`, | ||
| 'Team instance selector' | ||
| ) | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `(() => { | ||
| const button = document.querySelector('button[aria-label="Choose a team instance"]'); | ||
| if (!button) return false; button.click(); return true; | ||
| })()`, | ||
| 'Team instance selector action' | ||
| ) | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `(() => { | ||
| const option = [...document.querySelectorAll('[role="option"]')].find((item) => | ||
| item.innerText.includes(${JSON.stringify(team.id)})); | ||
| if (!option) return false; option.click(); return true; | ||
| })()`, | ||
| 'Saved team option' | ||
| ) | ||
| await waitFor( | ||
| evaluate, | ||
| signal, | ||
| `document.body.innerText.includes('Draft the implementation plan') && | ||
| document.body.innerText.includes('Prepare the implementation')`, | ||
| 'Saved task board in Teams settings' | ||
| ) | ||
| return { | ||
| passed: true, | ||
| observedBehavior: `Packaged Boss installed planning-only UAR teams ${team.id} and ${peer.id}, saved supervisor, bounded map/reduce and independent peer task graphs, restarted its sidecar, kept workspace ${otherWorkspaceId} isolated, and displayed the recovered board in Teams settings.` | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The design describes a three-route renderer boundary—snapshot, create, and add task—but this commit also exposes and invokes
prometheus.uar.teams.setup_starter. Because this paragraph defines the fixed IPC trust surface, omitting the fourth command makes the architecture record inaccurate; document all four routes or explicitly distinguish setup from the three planning operations.Useful? React with 👍 / 👎.