Context
The second half of #17559's law 3, extracted from #17560 at review: the move PR relocated 15 camelCase pure-function modules to apps/agentos/util/ as modules with imports rewritten. Converting those modules into classes rewrites call sites across the app and belongs in its own reviewable leaf. This ticket is the successor named by #17560's close-target contract, so the deferred work keeps a durable owner.
Intake V-B-A on 2026-08-23 narrowed one placement prescription before implementation. Parent #17559 says these modules become Body-style utility classes under apps/agentos/util/ (AgentOS.util.*), while the merged viewTopologyConformance.spec.mjs intentionally admits only UI-family classes under apps/agentos/view/**. A static utility extends Neo.core.Base; moving it under view/** would either fail that guard or weaken the suffix-equals-base-family law with a made-up UI family. The 15 classes therefore stay in the utility namespace. Descriptive class names (CockpitPresets, FleetStartPlan, and so on) preserve conceptual ownership without placing non-view code in the view tree.
The Problem
apps/agentos/util/ currently holds 15 camelCase function modules — Brain-style module shape in Body-side app code. Law 3's target is Body-style classes: PascalCase files, static members, Neo.setupClass, exact className, and generated class documentation.
The Architectural Reality
- Precedent:
src/util/* and apps/covid/Util.mjs are static-method classes extending Neo.core.Base, registered through Neo.setupClass.
- Live consumer graph re-measured on 2026-08-23:
viewerTime spans activity/catch-up/memories/tasks/wake; sourceHealth spans cockpit/detail/health/roster; agentFreshness spans detail/mailbox and the viewer-wake helper; telltale spans card/detail; addAgentFlow spans accounts/instances; configIntentRoundTrip spans accounts/detail; cockpitDockDocument also serves tours and Mission Control. The remaining helpers are currently single-surface or surface-shared, but they remain utility classes for one uniform, non-view class topology.
viewTopologyConformance.spec.mjs already enforces PascalCase, exact class identity, and UI base-family suffixes for view/**. This leaf extends the file/class-identity half over util/**; it does not apply UI suffix semantics to Neo.core.Base utility classes.
- The turn-loaded
AGENTS.md is 24,521 bytes against a 24,576-byte hard cap, and .claude/CLAUDE.md is a symlink to it. Two additive gate lines would break the loading budget and duplicate one source. This leaf rewrites the existing app-work trigger in place, byte-negatively, to carry laws 0+1 once.
The Fix
- Rename the 15 files in place to PascalCase and convert each module into an
AgentOS.util.* class extending Neo.core.Base, with static members and Neo.setupClass registration.
- Update every call site mechanically (
import {fn} from module becomes import Class plus Class.fn(...); exported constants become static properties). No helper behavior changes.
- Extend the conformance witness so
apps/agentos/util/** is PascalCase/class-only and every utility declares the exact path-derived className. Preserve the existing view/** UI-family suffix law unchanged.
- Rewrite the existing app-work trigger once, without increasing turn-loaded bytes: engine primitive before custom code; class suffix names its base family. Because
.claude/CLAUDE.md points to AGENTS.md, no duplicate harness edit exists.
- Regenerate docs JSON so all 15 classes land in the class hierarchy in the same commit.
Contract Ledger Matrix
| Target Surface |
Source of Authority |
Proposed Behavior |
Fallback / Edge Case |
Docs |
Evidence |
15 modules under apps/agentos/util/ |
#17559 law 3; src/util/*; merged #17560 topology guard |
become PascalCase AgentOS.util.* static classes in place |
current descriptive class names preserve scope; no non-view class enters view/** |
class docblocks |
consumer grep + class-resolution specs |
| existing imports and calls |
current source consumer graph |
default-import the owning class and invoke the same behavior through static members |
exported constants become static properties; no compatibility re-exports remain |
n/a |
zero old-path / named-function-import grep |
| conformance witness |
#17560's fail-closed spec |
covers util/** PascalCase, class-only shape, and exact path-derived className; existing view/** suffix law remains unchanged |
unresolved class or missing identity is an offender, never a skip |
spec docblock |
the witness itself |
| app-work trigger |
AGENTS.md §edge_case_triggers; ADR 0007; /turn-memory-pre-flight |
byte-negative rewrite adds laws 0+1 to the existing app-work gate once |
.claude/CLAUDE.md inherits through its symlink; additive duplicate lines are forbidden by the 24 KiB cap |
trigger text |
size guard + loader/symlink receipt |
| generated class docs |
generate-docs-json |
records all 15 AgentOS.util.* classes |
generation failure blocks delivery |
docs/output/class-hierarchy.json |
generated diff + docs guard |
Acceptance Criteria
Out of Scope
Any behavior change inside the helpers; per-view primitive migrations (sibling leaves); ntype renames (settled in #17560: registry keys, not base-class claims); multi-tenant Knowledge Base ingestion or kbSync changes.
Related
Parent: #17559. Predecessor: #17560 (the move; its review round 1 mandated this successor). Precedent: src/util/*, apps/covid/Util.mjs.
Live intake sweep: #17568 is open and unassigned; no PR or remote branch targets it; the only open PR at intake (#17592) is unrelated. Memory Core returned no relevant prior implementation mapping. The Knowledge Base query missed this same-day ticket and is explicitly non-authoritative for this lane because its Neo ingestion is stale.
Origin Session ID: 28bee2e0-4dc8-4375-8514-78fcf38d0d30
Intake correction evidence: parent #17559, PR #17566 review/response, live viewTopologyConformance.spec.mjs, src/util/*, AGENTS size guard, and .claude/CLAUDE.md symlink inspected 2026-08-23.
Retrieval Hint: query_raw_memories("fleet util modules become classes owned placement cockpit builders")
Context
The second half of #17559's law 3, extracted from #17560 at review: the move PR relocated 15 camelCase pure-function modules to
apps/agentos/util/as modules with imports rewritten. Converting those modules into classes rewrites call sites across the app and belongs in its own reviewable leaf. This ticket is the successor named by #17560's close-target contract, so the deferred work keeps a durable owner.Intake V-B-A on 2026-08-23 narrowed one placement prescription before implementation. Parent #17559 says these modules become Body-style utility classes under
apps/agentos/util/(AgentOS.util.*), while the mergedviewTopologyConformance.spec.mjsintentionally admits only UI-family classes underapps/agentos/view/**. A static utility extendsNeo.core.Base; moving it underview/**would either fail that guard or weaken the suffix-equals-base-family law with a made-up UI family. The 15 classes therefore stay in the utility namespace. Descriptive class names (CockpitPresets,FleetStartPlan, and so on) preserve conceptual ownership without placing non-view code in the view tree.The Problem
apps/agentos/util/currently holds 15 camelCase function modules — Brain-style module shape in Body-side app code. Law 3's target is Body-style classes: PascalCase files, static members,Neo.setupClass, exactclassName, and generated class documentation.The Architectural Reality
src/util/*andapps/covid/Util.mjsare static-method classes extendingNeo.core.Base, registered throughNeo.setupClass.viewerTimespans activity/catch-up/memories/tasks/wake;sourceHealthspans cockpit/detail/health/roster;agentFreshnessspans detail/mailbox and the viewer-wake helper;telltalespans card/detail;addAgentFlowspans accounts/instances;configIntentRoundTripspans accounts/detail;cockpitDockDocumentalso serves tours and Mission Control. The remaining helpers are currently single-surface or surface-shared, but they remain utility classes for one uniform, non-view class topology.viewTopologyConformance.spec.mjsalready enforces PascalCase, exact class identity, and UI base-family suffixes forview/**. This leaf extends the file/class-identity half overutil/**; it does not apply UI suffix semantics toNeo.core.Baseutility classes.AGENTS.mdis 24,521 bytes against a 24,576-byte hard cap, and.claude/CLAUDE.mdis a symlink to it. Two additive gate lines would break the loading budget and duplicate one source. This leaf rewrites the existing app-work trigger in place, byte-negatively, to carry laws 0+1 once.The Fix
AgentOS.util.*class extendingNeo.core.Base, with static members andNeo.setupClassregistration.import {fn} from modulebecomesimport ClassplusClass.fn(...); exported constants become static properties). No helper behavior changes.apps/agentos/util/**is PascalCase/class-only and every utility declares the exact path-derivedclassName. Preserve the existingview/**UI-family suffix law unchanged..claude/CLAUDE.mdpoints toAGENTS.md, no duplicate harness edit exists.Contract Ledger Matrix
apps/agentos/util/src/util/*; merged #17560 topology guardAgentOS.util.*static classes in placeview/**util/**PascalCase, class-only shape, and exact path-derivedclassName; existingview/**suffix law remains unchangedAGENTS.md§edge_case_triggers; ADR 0007;/turn-memory-pre-flight.claude/CLAUDE.mdinherits through its symlink; additive duplicate lines are forbidden by the 24 KiB capgenerate-docs-jsonAgentOS.util.*classesdocs/output/class-hierarchy.jsonAcceptance Criteria
apps/agentos/util/orapps/agentos/view/**; all 15 former modules are registeredAgentOS.util.*classes.util/**class identity and stays green; the existingview/**suffix-family law is neither weakened nor broadened to non-view helpers; the full AgentOS unit tree stays green with zero behavior change.generate-docs-jsonlands all 15 classes in the same commit.Out of Scope
Any behavior change inside the helpers; per-view primitive migrations (sibling leaves); ntype renames (settled in #17560: registry keys, not base-class claims); multi-tenant Knowledge Base ingestion or
kbSyncchanges.Related
Parent: #17559. Predecessor: #17560 (the move; its review round 1 mandated this successor). Precedent:
src/util/*,apps/covid/Util.mjs.Live intake sweep: #17568 is open and unassigned; no PR or remote branch targets it; the only open PR at intake (#17592) is unrelated. Memory Core returned no relevant prior implementation mapping. The Knowledge Base query missed this same-day ticket and is explicitly non-authoritative for this lane because its Neo ingestion is stale.
Origin Session ID: 28bee2e0-4dc8-4375-8514-78fcf38d0d30 Intake correction evidence: parent #17559, PR #17566 review/response, live
viewTopologyConformance.spec.mjs,src/util/*, AGENTS size guard, and.claude/CLAUDE.mdsymlink inspected 2026-08-23. Retrieval Hint:query_raw_memories("fleet util modules become classes owned placement cockpit builders")