- umbrella term 'work record'; intake kind 'intake item' - any repo file is a work-record source; generated per-repo index (transclusion-style, markitect scope) replaces separate authored file - engagements are their own kind - ordering simple (lane+priority+age); deps optional; WSJF optional, situational overload-triage only - issue-core repositioned as third-party tracker connector (UUID<-> external id, boundary sync), out of the internal loop - fresh intake entity in state-hub; suggestions to read-only legacy - budgets hybrid: repo budgets.yaml + frontmatter override + global best-guess default; enforcement programmatic, never agentic Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
18 KiB
Work Orchestration Architecture — Draft for Discussion
Draft v0.2 — 2026-07-20 (v0.1 2026-07-19). Synthesis of
2026-07-19-work-orchestration-infrastructure-survey.md(internal) and2026-07-19-work-orchestration-best-practices.md(external). Trigger: reconciling the binky-control automation queues with the suggestion/task/workplan backbone (AWQ-010 case). Status: proposal, founder-reviewed — the seven open questions were resolved with the founder on 2026-07-20 (§8); terminology extensions are marked ⟨new⟩ and land as canon standard drafts next.
0. Thesis
The queues were not a mistake — they were a market signal. Intake through a one-YAML-block append beat the hub suggestion flow because its friction was an order of magnitude lower, and it carried routing information (lanes) the backbone lacks. The error was ontological, not practical: the queue items became a second, unindexed species of work item.
The reconciliation is therefore not "move queues into the hub" but: make the backbone cheap enough at intake that the queue pattern becomes a schema-valid, hub-indexed instance of it — and make every future species impossible to create outside the reconciliation loop without tripping an alarm.
One sentence: everything with an ID is a work record, every work record is a repo file, every repo file is indexed, and every list anyone works from is a derived view.
1. The unified ontology
1.1 Work record ⟨new umbrella term⟩
A work record is any identified, lifecycle-bearing coordination artefact. All existing species become kinds of work record:
work record
├── intake item ⟨new⟩ — spark: idea, mail finding, observation, request
│ (absorbs: AWQ items, hub suggestions, "actionable" mail-triage rows)
├── task — smallest executable unit (canon lifecycle)
│ (absorbs: workplan tasks, adhoc tasks, issue-core issues-as-interface)
├── workplan — structured, dependency-bearing body of work
├── decision — approval package + resolution
│ (absorbs: DEC items; hub decision records become the index)
├── engagement ⟨new⟩ — scheduled external interaction
│ (absorbs: OH items — office-hour/command-day bookings)
└── register entry — risk, technical debt, capability request, CCR,
interface change (existing species, unchanged
semantics, brought under the same conventions)
Every work record, regardless of kind, carries the same conventional spine (aspect 4 — one framework, many demands):
| Field | Convention |
|---|---|
id |
canonical name per type registry (§1.3) |
uuid |
UUIDv7, hub-assigned at first index, written back to the file (like state_hub_*_id today) |
kind |
from the closed kind list above |
lane |
green/blue/yellow/orange/red (§2) |
status |
abstract canon lifecycle for its kind (§1.4) |
owner |
repo-anchored owner (agent identity or human) |
repo |
owning repo (ADR-001/ADR-005 anchor) |
tags |
policy + derived tags (§3) |
created/updated |
dates; UUIDv7 provides fine-grained internal time |
Kind-specific fields (budget, evidence, deadline_pressure, attention_cost, …) extend the spine; they never replace it.
1.2 File-first, one reconciliation loop (aspect 3)
- Every work record originates in a repo file — and any file in a repo is a potential source of work records. Workplans stay dedicated files; intake items, decisions, engagements are YAML blocks authored in context: thematic live documents, meeting notes, design docs, triage logs. The schema is the contract, not the location. (Motivating example: meeting notes with todos are kept where the work happens, yet the todos must surface in one global orientation view — see the index below.)
- A fully generated per-repo work-record index captures every registered record with a reference to its source location — a transclusion-style view: the record lives once in its source file, the index (and other views) reference it. Registration binds each record to hub state wherever it was authored, which stabilizes concurrent human/agent edits and enables cross-checks instead of causing conflicts. Efficient transclusion and index management is markitect-family scope.
- Forgejo CI validates schemas on push (yaml-block schema per kind — the GitOps "validate before apply" rule).
fix-consistencygrows checks per kind (as C-06 does for workplans): registers new records, writes UUIDs back, syncs status from file to hub. The hub stays a read model; runtime operations data (logs, metrics, run histories, token events) is the sanctioned exception and lives only in hub/activity-core/monitoring stores.- Derived views (§4) are generated from the hub, never hand-maintained:
the "Completed" tail of AutopilotWorkQueue.md, daily-brief queue summaries
etc. become materialized view output (ADR-003 pattern), clearly marked
[auto].
1.3 Type registry and layered identity (aspect 5)
A single canon file (canon/standards/work-record-types_v0.1.md + machine
copy work-record-types.yaml) registers every id scheme:
- code: WP # {PREFIX}-WP-NNNN workplan
- code: T # {WP-ID}-TNN task
- code: IN # {PREFIX}-IN-NNNN intake item ⟨new; AWQ-* grandfathered⟩
- code: DEC # {PREFIX}-DEC-YYYY-NNN decision
- code: ENG # {PREFIX}-ENG-YYYY-NNN engagement ⟨OH-* grandfathered⟩
- code: RISK, TD, CAP, CCR, ADR … register entries (existing)
- UUIDv7 is primary for bookkeeping, relations, dependencies, history — stable across renames, migrations, and terminology transitions (the workstream→workplan migration cost is the proof this matters).
- Canonical names are primary for humans and agents in UI/CLI/API output, prose, and commits; UUIDs inspectable on demand. This is already practiced for WP/T — the registry universalizes it.
- Grandfathering rule (from the terminology standard): existing ids (AWQ-, OH-) are never renamed; the registry maps legacy prefixes to kinds and the legacy-meter discipline governs retirement of the schemes, not the records.
1.4 Abstract states + flow profiles (aspect 4)
- The abstract lifecycles stay minimal and canon-fixed: task
wait/todo/progress/done/cancel(InfoTechCanon); workplanproposed/ready/active/blocked/backlog/finished/archived; intake itemopen → vetted → routed → closed(promoted|declined|absorbed)⟨new, replaces both suggestion stages and queue-item ad-hoc states⟩; decisionprepared → resolved(approved|rejected|revised|deferred); engagementqueued → prepared → done. - Different work kinds (development, ops, compliance, billing, sales…)
get their demands met by flow profiles on the existing task-flow
engine: named workstation definitions whose entry/exit assertions encode
the kind-specific gates (e.g. a compliance task cannot exit
progresswithout an evidence link; a sales engagement cannot enterpreparedwithout prepared_material). Profiles are YAML in repos → same reconciliation loop. States stay abstract; rigor lives in assertions.
2. Lanes become fleet canon (aspects 1, 6, 7)
Promote binky-control's AutonomyPolicy lane model to
canon/standards/autonomy-lanes_v0.1.md, unchanged in substance:
- Green (safe autonomous) · Blue (reversible, logged) · Yellow (deferred approval) · Orange (time-window approval) · Red (human-only).
laneis a mandatory field on every work record and on every agent manifest; the harness already enforces lane-scoped tool profiles — this closes the loop from item to execution permission.- Human attention is modeled as a WIP-limited workstation: the founder attention budget (minutes/day, max decisions/day) is a lane capacity; Yellow routes to the decision queue view, Orange to the engagement view, both subject to that capacity. External research is unanimous that risk-tiered gating is the only scalable human-oversight pattern.
- Time-boxing (
needed_by,fallback_if_no_response) is hub-owned: the hub (not a human reading a file) raises the escalation when a window lapses — via the existing NATS → activity-core delegation.
3. Policy tags, not states (aspect 6)
Two tag classes, both in the tags field, never in status:
- Derived tags — computed by hub/activity-core from data, read-only in
files:
stalled(work-item age over threshold — the flow-metrics form),stale(reviewed against older repo state),overdue-decision,budget-breached,orphaned(no goal/workplan link). - Policy tags — assigned by agents/humans under tagging policies:
automatable,needs-human,unclear-implementation,red-adjacent(touches a Red boundary),compliance-relevant. Tagging policies are YAML rules (activity-coreActivityDefinitions may auto-apply them on events) — auditable and evolvable without schema migrations.
Tags drive the optimization loop: automatable feeds the autopilot view;
needs-human + lane feeds attention routing; stalled+unclear feeds the
kaizen coach. This is how "what should be automated next" becomes a query
instead of a discovery.
4. Queues are views (the reconciliation itself)
The three binky queues are retained as products, redefined as views:
| Today | Becomes |
|---|---|
| AutopilotWorkQueue.md (hand-edited) | Autopilot view: kind=intake∨task, lane∈{green,blue}, status∈{open,todo}, tag=automatable, ordered by lane+priority+age (§4.1); "never empty" becomes a monitored invariant with an alert, not a plea |
| DecisionQueue.md open section | Decision view: kind=decision, status=prepared, ordered by needed_by; resolved log is generated |
| OfficeHourQueue.md | Engagement view: kind=engagement, batched per command day; runbook generation stays (officehour/) |
| — (new) | Per-repo work-record index (§1.2): every registered record, any kind, any source file — the "all my todos across everything" orientation view |
Mechanics: records are authored in their source files (cheap intake
preserved — one YAML block, schema-checked, authored where the context is);
fix-consistency indexes them; view documents and the index are
regenerated [auto]. Pull protocol becomes a status transition
(todo → progress with owner) instead of a prose note — which makes claims
visible fleet-wide and collision-free.
4.1 Ordering (resolved 2026-07-20)
Default ordering is deliberately simple: lane + priority + work-item age — zero extra fields at intake. Dependency capture is optional per record (edges when they matter, never mandatory). WSJF is an optional, situational triage instrument for capacity-overload situations, applied to a view when selection pressure justifies estimation cost — never a required field. Rationale: small tasks implement faster than they can feasibly be planned and evaluated beforehand; mandatory estimation is waste at the bottom of the size distribution.
4.2 issue-core: third-party connector, not core loop (resolved 2026-07-20)
issue-core's role is a connector framework to external issue-tracking systems (Forgejo issues, Jira, GitHub, …): it maintains the mapping between internal work-record UUIDs and external issue ids and provides two-way, transclusion-like sync at the boundary when we collaborate through a third-party tracker. It is not part of the internal execution loop — claiming and execution run on work records directly (harness intake via NATS/activity-core per its stated target). Until an external integration is actually switched on, the connector layer adds zero load.
Promotion (the AWQ-010 lesson) becomes a first-class transition:
intake.routed → workplan|task|decision|engagement with back-links
(promoted_to/origin), performable by one CLI/MCP call that writes both
files and re-syncs — never a manual transcription again.
5. Visibility, tenancy, scale (aspect 2)
- state-hub remains the dev-coordination read model (localhost, file-first, fix-consistency).
- core-hub is the production visibility layer: dashboards, flow metrics (WIP, cycle time, item age, throughput — all computable from UUIDv7 + status transitions), cross-domain rollups, operator console. The work record spine becomes part of the core-hub contract/IR so Gen-3 ingests ADR-001 artefacts natively (its workplan-coordination spec already commits to this).
- Tenancy: repo → domain → tenant, derived from repo classification (never a parallel spine). agent-harness is already multi-tenant with per-tenant credential lanes (ops-warden/OpenBao); dashboards get tenant/domain scoping from the same derivation. Separation is by classification + credential lanes; centralization is by shared runtime (harness, activity-core, hubs) — efficient utilization with clean blast radii.
- Budgets as guardrail envelopes (aspect 1, resolved 2026-07-20):
three-level resolution, strictly programmatic —
- workplan frontmatter override (narrowest, lives with the work),
- per-repo
budgets.yamllane/default envelopes (the auditable guardrails), - global best-guess default when neither exists — nothing ever runs
unbounded.
The harness resolves override → repo policy → global default at run
start;
record_token_eventmeters;budget-breachedtag + escalation on breach. Budget resolution and enforcement is automation infrastructure code, never agentic effort — AI tokens and personal review only refine the estimates over time. Complex-project risk control = dependency edges (existing) + lane gates + budget envelopes + flow metrics, all on one spine.
6. Opinionated by construction (aspect 7)
- The registry is closed. An id pattern not in the type registry fails CI and fix-consistency (the "sidetrack detector" — a warning that names the reintegration cost explicitly). New kinds are added by canon PR, which is deliberately cheap (one YAML entry + one schema) so the legitimate path is easier than the workaround — the core lesson of the queues.
- AGENTS.md generation (state-hub templates) teaches the conventions in every repo; the guidance and the enforcement come from the same source.
- Intake friction budget: creating an intake item must never cost more than the AWQ pattern did (append one block). If a future workaround appears anyway, the postmortem question is fixed: which friction did we fail to remove?
7. Migration path (staged, history-preserving)
- Canonize (docs only): work-record umbrella + type registry +
autonomy-lanes standard + intake lifecycle → canon standards v0.1;
extend
workplan-terminology-fleetrather than fork it. - Schema + CI: YAML schemas per kind; Forgejo CI check; binky-control is the pilot (its queues become schema-valid without renaming ids).
- Index: fix-consistency checks for intake/decision/engagement kinds (C-2x series); hub tables reuse existing entities where they fit (suggestions table ⇢ intake items — suggestions gain file artefacts, resolving their own ADR-001 tension; decisions already dual-exist, the check just automates the mirroring both ways).
- Views: generated
[auto]sections replace hand-maintained queue tails; hub owns needed_by clocks via NATS → activity-core. - Tags + metrics: derived-tag computation and the four flow metrics in state-hub first, surfaced in core-hub dashboards as the contract lands.
- Retire legacy singularities per legacy-meter discipline (e.g. the hub-first suggestion create path) only after zero measured use.
Each stage is independently valuable; stopping after any stage leaves the system consistent (no big-bang).
8. Resolved questions (founder review, 2026-07-20)
- Naming: umbrella term is work record; the intake kind is intake item ("suggestion" remains only as the legacy bridge name). Mental model: a task is a specific kind of work record — the smallest executable unit; the work-record supertype guarantees the shared spine across all kinds.
- Where records live: any repo file is a potential source of work records (meeting notes, thematic docs, design docs); a fully generated per-repo index references all registered records in their source locations (transclusion-style; markitect-family tooling). See §1.2. Registration binds records to hub state wherever authored — stabilization and cross-checking for concurrent human/agent work, not a conflict source.
- Engagements are their own kind — a command day batches many engagements; an engagement may spawn or resolve several decisions but is not itself one.
- Ordering: simple default (lane + priority + age); dependency capture optional; WSJF optional and situational — a triage instrument for limited capacity under overload, not a universal method. Small tasks implement faster than they can feasibly be planned and evaluated. §4.1.
- issue-core is a connector framework to third-party issue trackers (internal UUID ↔ external issue-id mapping, two-way transclusion-like sync at the boundary); not part of the internal loop; must add no load until an integration is actually used. §4.2.
- Hub entity: fresh intake entity with the clean UUIDv7 spine; the suggestions table becomes read-only legacy with a one-time close-out of its few records.
- Budgets: hybrid — per-repo
budgets.yamlguardrails + workplan-frontmatter overrides + a global best-guess default as final fallback so nothing runs unbounded; resolution/enforcement is programmatic infrastructure, never agentic effort. §5.
Prepared from live fleet data (AWQ-010 reintegration, DEC-2026-003/004 dual bookkeeping, BINKY-WP-0002 slug drift) and the two research artefacts alongside this file. All open questions resolved with the founder 2026-07-20. Next step: canon standard drafts (work-record types, autonomy lanes, intake lifecycle) + a CUST workplan for migration stages 1–2.