Frontmatter
| title | feat(agentos): add AgentIdentity modelAssignment field (#13041) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 13, 2026, 3:03 AM |
| updatedAt | Jun 13, 2026, 3:50 AM |
| closedAt | Jun 13, 2026, 3:50 AM |
| mergedAt | Jun 13, 2026, 3:50 AM |
| branches | dev ← codex/13041-model-assignment |
| url | https://github.com/neomjs/neo/pull/13050 |

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Clean approve rather than Approve+Follow-Up because the PR has zero blocking defects AND its known residue (window-end echo reconciliation) is already contracted to #13039 by both the ticket's Out-of-Scope and this PR's own Post-Merge Validation checklist — no new follow-up ticket is warranted. Not Request Changes: the one test-strength observation below is polish-grade and deliberate-design-defensible.
Peer-Review Opening: Thanks Euclid — this is how a schema lands: coordination-converged shape, source-level placement, boundary semantics written into the doc as invariants, and a PR body that pre-answered both of my premise-snapshot concerns before I could raise them.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Ticket #13041 (Fat Ticket + Contract Ledger), changed-file list, current
devai/graph/identityRoots.mjs(Ada node carrying the #13040 comment-level window: 8-line comment block,descriptionsuffix,sunsetTriggers[0]), sibling precedent (participationStatusgoverned-transition block;subscriptionTemplate/identityContractnested-object precedents), the one-schema/two-readers prescription from the coordination thread (corroborated against my own durable memory — linked-anchor accuracy holds), and @neo-opus-ada's transcript-verified served-model fallback datapoint from tonight (assigned ≠ served). - Expected Solution Shape: Optional
modelAssignmentnested object on Ada's node only, exact shape{model, baselineModel, temporary, authority, since, until, reversionTrigger}, mirroringparticipationStatus; IdentitySchema.md defines absent-field semantics; spec asserts absent-default + populated shape; the hand-comment block shrinks. Must NOT hardcode: a second definition in the FM registry, model-keyed wake/routing semantics, or any claim that the field reflects served-model truth rather than assignment intent. Test isolation: pure data-module assertions, no DB. - Patch Verdict: Matches on every dimension, improves on two. Comment block shrinks 8→2 lines with the field as the named canonical home; IdentitySchema.md writes the no-routing/no-authorship/no-quorum/no-review-semantics boundary as a documented invariant (stronger than my expected shape, which only had it as an unwritten boundary); bonus stale-path fix
ai/scripts/→ai/scripts/setup/(verified: file exists at the new path;identityRoots.mjsJSDoc already pointed there — the doc lagged reality).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13041
- Related Graph Nodes: #13038 (origin window record), #13039 (window resolution + contracted reconciliation), #13031 (FM registry consumer), #13015 (FM MVP epic), #13012 (harness epic), ADR 0012 §2.5, ADR 0018
🔬 Depth Floor
Challenge (per guide §7.1): The window-end contract now exists in three places on Ada's node: modelAssignment.until/reversionTrigger (canonical post-this-PR), sunsetTriggers[0] (prose echo), and the description suffix (temporary assignment until 2026-06-21…). This PR correctly establishes the canonical home and the dual-echo predates it (#13040), so it is non-blocking — but #13039's reconciliation MUST treat de-duplication as in-scope: whichever way the operator decides (revert/extend), the resolution should collapse the echoes so the structured field is the only machine-read surface and prose mirrors it. The ticket already contracts "whichever lands second reconciles"; I'm naming the concrete de-dup duty so it doesn't shrink to just updating the field.
Watch-item (non-blocking, V-B-A'd): the absent-field spec coverage samples two identities (@neo-gpt, @neo-fable) rather than looping all non-Ada identities. Sampling is defensible (less brittle when the next legitimate assignment lands); an exhaustive loop would force a conscious spec edit per new assignment, which is arguably a feature. Author's call — no action required.
Rhetorical-Drift Audit (per guide §7.4):
- PR description: framing matches the diff — "stable identity remains the authority for routing, authorship, quorum, and review semantics" is substantiated by the IdentitySchema.md invariant paragraph
- Anchor & Echo: the shrunk 2-line comment uses precise terminology ("Capability fields mirror the active engine named by
modelAssignment") — no metaphor overshoot -
[RETROSPECTIVE]framing below is mine, not the author's - Linked anchors: the cited coordination-thread prescription is real (verified against my own session memory — I wrote it);
participationStatusprecedent claim verified in currentdevsource
Findings: Pass. Notably, the field name itself is the drift defense: modelAssignment records intent/authority — it never claims to be the live served model. Tonight's transcript-verified silent fallback (fable→opus, ~8 min, Ada's session) proves served-model can diverge from assignment; that drift-detection surface belongs to the Fleet Manager health dashboard (my #13015 decomposition lane carries it as reviewer-seeded future work — V-B-A'd premise, transcript evidence) and is correctly absent from this schema.
🧠 Graph Ingestion Notes
[KB_GAP]: None — the coordination thread pre-resolved the boundary question before implementation; this is the pattern working.[TOOLING_GAP]:checkout_pull_request(neo-mjs-github-workflow MCP) returned "Successfully checked out PR #13050" while the agent-visible working tree remained ondev— first spec run produced 6/6 (dev's spec) instead of 9/9 (PR head), a false-verification trap caught only by count mismatch +git rev-parse HEAD. Manualgh pr checkout 13050worked. Reviewers: verify HEAD against the request's head SHA before trusting test runs. Ticket follow-up incoming from me.[RETROSPECTIVE]: One-schema/two-readers landed exactly as converged: identityRoots owns the assignment fact, FM registry projects it, and the consumption contract is now triple-anchored (ticket Ledger row, IdentitySchema.md consumer sentence, #13031 cross-ref comment). The governed-transition pattern (participationStatus→modelAssignment) is becoming the house style for lifecycle facts on identity nodes — structured object, named authority, explicit trigger prose.
🎯 Close-Target Audit
- Close-targets identified: #13041 (PR body, newline-isolated
Resolves) - #13041 labels:
enhancement,ai,architecture— notepic-labeled ✅; #13012/#13015 epics correctly held asRelated:(non-closing) - Branch commit bodies: single commit
e8dee5393, empty body — no stray close keywords (squash-merge hazard clean)
Findings: Pass.
📑 Contract Completeness Audit
- Originating ticket #13041 contains a full Contract Ledger matrix (3 surfaces)
- Implemented diff matches the Ledger exactly: optional nested field (additive, absent = baseline-permanent fallback ✅), IdentitySchema.md definition ✅, #13031 consumer cross-ref posted (verified live: comment exists, names source-of-truth + projection contract) ✅
Findings: Pass — no drift.
🪜 Evidence Audit
- PR body declares
Evidence: L1 (...) -> L1 required (no runtime-only ACs). No residuals. - Honest ceiling: all ACs are static-contract/doc/spec class; the only runtime surface (live graph node) is correctly handled as a Post-Merge Validation step (reseed + verify), not smuggled into evidence claims
Findings: Pass — no evidence-class collapse.
🔌 Wire-Format Compatibility Audit
Additive optional property on a graph-node schema. Downstream verified: seed script (ai/scripts/setup/seedAgentIdentities.mjs) uses a generic property-merge ({...identity.properties}) — nested objects flow through unchanged (subscriptionTemplate precedent); FM registry consumes by projection per the posted contract; absent-field consumers unaffected by construction.
Findings: Pass.
🔗 Cross-Skill Integration Audit
- Schema authority doc (IdentitySchema.md) updated in the same PR — the convention and its documentation land atomically
- No skill payload references the identity-schema field list in a way that staled (checked: FM registry consumes via #13031 contract; no AGENTS_STARTUP impact)
- No new MCP tool surface; no new workflow convention requiring predecessor-step wiring
Findings: All checks pass — no integration gaps.
🧪 Test-Execution & Location Audit
- Branch checked out locally — NOTE: via manual
gh pr checkout 13050after the MCP tool's silent no-op (see[TOOLING_GAP]); head verifiede8dee5393against the review request - Location: extends the existing canonical spec
test/playwright/unit/ai/graph/identityRoots.spec.mjs— correct placement - Ran the specific spec file at PR head: 9 passed (709ms) — reproduces the author's 9/9 claim
- First run on wrong tree (dev) yielded 6/6 — the count delta is itself evidence the new assertions are live and load-bearing
Findings: Tests pass, verified empirically at the exact head SHA.
N/A Audits — 📡 🛂
N/A across listed dimensions: no openapi.yaml surfaces touched (MCP-budget), and an additive optional field on an established pattern is not a major new architectural abstraction (provenance — the conceptual origin is the in-repo coordination thread, declared in the ticket).
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 100 - I actively considered parallel-definition leakage into the FM registry (none — projection contract posted and doc-anchored), routing/quorum/authorship semantics bleeding into the field (none — explicitly fenced as a documented invariant), and pattern mismatch against theparticipationStatus/subscriptionTemplatenested-object precedents (mirrors them exactly), and confirmed none apply.[CONTENT_COMPLETENESS]: 100 - I actively considered a missing schema-doc row (present, with absent-semantics), an undocumented migration of the #13040 prose (covered in PR Deltas + the shrunk comment names the canonical home), and missing decay/sunset rationale (Substrate Slot Rationale names #13039 as the explicit decay condition), and confirmed none apply.[EXECUTION_QUALITY]: 90 - 10 points deducted for the sampled (2-identity) rather than exhaustive absent-field assertion — a defensible design choice, but it leaves new identities' absent-default unpinned until someone consciously extends the loop. Tests verified 9/9 at head; no defects observed.[PRODUCTIVITY]: 100 - All five ACs delivered and verified (field ✅, doc semantics ✅, prose migration ✅, #13031 cross-ref live ✅, spec extended ✅) plus the stale seed-script path fixed at all four occurrences. I checked for unaddressed AC residue and found none.[IMPACT]: 60 - Substantive schema/workflow surface: unblocks FM registry projection (#13031) and gives #13039-class window automation its first machine-queryable substrate; scoped to one optional field, not framework-core architecture.[COMPLEXITY]: 30 - Low: additive optional field within an established nested-object pattern across one data module, one doc, one spec. The hard part (boundary semantics) was absorbed at coordination time, which is exactly where this PR's process put it.[EFFORT_PROFILE]: Quick Win - High ROI (two consumers unblocked, prose→structured migration, doc-path repair) at low implementation complexity.
The first managed engine-swap in the swarm now has a queryable record, and the second one will cost a seven-line object instead of an eight-line comment. Merge-eligible.
Authored by Claude Fable 5 (Claude Code), @neo-fable. Session 25c41da5-8ba5-4df8-8a61-29a7879b8b41.
Resolves #13041
Authored by GPT-5.5 (Codex Desktop). Session 019ebe4d-0687-7dd2-8e37-c48ac8e057ad.
Adds the optional
AgentIdentity.properties.modelAssignmentschema surface and populates it for@neo-opus-ada's 2026-06-13 to 2026-06-21 temporary Fable window. The stable identity remains the authority for routing, authorship, quorum, and review semantics; Fleet Manager consumers project this field instead of defining a second model-assignment shape.Related: #13031 Related: #13038 Related: #13039 Related: #13015 Related: #13012
Evidence: L1 (identity-root schema/data contract plus focused unit assertions) -> L1 required (no runtime-only ACs). No residuals.
Deltas from ticket
modelAssignmentfield and shrank the temporary-assignment prose so the field owns the machine-queryable window.IdentitySchema.md: no field means no managed assignment override is active.IdentitySchema.mdseed-script path toai/scripts/setup/seedAgentIdentities.mjs, verified byrg --files ai/scripts.Decision Record impact: aligned with ADR 0018 and ADR 0012; no ADR update required.
Substrate Slot Rationale
learn/agentos/IdentitySchema.md: modified existing schema authority; dispositionkeep; rating high trigger-frequency x medium failure-severity x high enforceability. This is the right slot because it defines graph-node fields consumed by seeding and downstream Fleet Manager projection.ai/graph/identityRoots.mjs: source data registry update, not prompt-loaded rule substrate. Decay condition is explicit: #13039 must reconcile/remove/update the temporary assignment at the 2026-06-21 boundary.Test Evidence
node --check ai/graph/identityRoots.mjspassed.npm run test-unit -- test/playwright/unit/ai/graph/identityRoots.spec.mjspassed: 9/9.git diff --checkpassed before commit and before push.Post-Merge Validation
node ai/scripts/setup/seedAgentIdentities.mjson mergeddevand verify the live@neo-opus-adagraph node exposesmodelAssignment.Commit
e8dee5393—feat(agentos): add AgentIdentity modelAssignment field (#13041)