Work Packages: Structured Agent Identity & Constitution-Profile Integration

Inputs: Design documents from kitty-specs/048-structured-agent-identity-and-constitution-profile-integration/ Prerequisites: plan.md (complete), spec.md (complete), research.md (complete), data-model.md (complete), contracts.md (complete), quickstart.md (complete)

Tests: Included as integrated verification within each WP (not separate test-only WPs).

Organization: Fine-grained subtasks (Txxx) roll up into work packages (WPxx). Each work package is independently deliverable and testable. Two parallel tracks converge in WP08.

Prompt Files: Each work package references a matching prompt file in tasks/ generated by /spec-kitty.tasks.


Work Package WP01: ActorIdentity Dataclass & StatusEvent Backwards Compat (Priority: P0)

Goal: Introduce the ActorIdentity frozen dataclass as the structured 4-part agent identity primitive and update StatusEvent.actor to use it with full backwards compatibility. Independent Test: Create an ActorIdentity from a compound string, serialise it via StatusEvent.to_dict(), deserialise with StatusEvent.from_dict(), and verify all four fields survive the round-trip. Read a legacy bare-string event and confirm it produces a valid ActorIdentity. Prompt: tasks/WP01-actor-identity-dataclass.md Requirement Refs: FR-001, FR-002, NFR-001, NFR-004, C-001

Included Subtasks

  • ✅ T001 Create ActorIdentity frozen dataclass in src/specify_cli/identity.py
  • ✅ T002 Implement serialisation methods (to_dict, from_dict, to_compact, from_compact, from_legacy) with field validation
  • ✅ T003 Modify StatusEvent.actor type from str to ActorIdentity in src/specify_cli/status/models.py
  • ✅ T004 Update StatusEvent.to_dict() and from_dict() for backwards-compatible actor serialisation
  • ✅ T005 Update _guard_actor_required() in src/specify_cli/status/transitions.py to accept ActorIdentity

Implementation Notes

  • ActorIdentity is a frozen dataclass with four str fields: tool, model, profile, role
  • Compound string format: tool:model:profile:role with : separator
  • Partial strings fill from right with "unknown" (e.g., "claude:opus"tool="claude", model="opus", profile="unknown", role="unknown")
  • StatusEvent.to_dict() emits actor as a dict; from_dict() detects str vs dict and constructs ActorIdentity accordingly
  • No changes to store.py — it delegates to to_dict()

Parallel Opportunities

  • Track A WP01 runs in parallel with Track B WP04 (no shared dependencies)

Dependencies

  • None (starting package for Track A)

Risks & Mitigations

  • Breaking existing event log reads → extensive from_dict() detection logic for str/dict
  • Type annotation changes cascade → limit to models.py, transitions.py in this WP

Work Package WP02: WP Frontmatter Structured Agent (Priority: P1)

Goal: Update frontmatter read/write for work package files to support structured agent identity as a YAML mapping while preserving backwards compatibility with scalar string values. Independent Test: Write a structured agent mapping to a WP frontmatter file, read it back, and verify it produces the same ActorIdentity. Read a legacy scalar agent: "claude" and verify it becomes ActorIdentity(tool="claude", ...). Prompt: tasks/WP02-frontmatter-structured-agent.md Requirement Refs: FR-004, NFR-004, C-002

Included Subtasks

  • ✅ T006 Update frontmatter read path in src/specify_cli/frontmatter.py to handle structured agent (str → from_legacy, dict → from_dict)
  • ✅ T007 Update frontmatter write path to always emit structured YAML mapping when value is ActorIdentity
  • ✅ T008 Update WorkPackage.agent property in src/specify_cli/tasks_support.py to return ActorIdentity | None
  • ✅ T009 Handle edge case: unknown tool names stored and round-tripped faithfully

Implementation Notes

  • extract_scalar() currently uses regex to extract string values — needs to also handle YAML mapping
  • Use ruamel.yaml for parsing frontmatter blocks that contain structured agent
  • Write path always uses structured YAML mapping: agent:\n tool: claude\n model: opus\n profile: impl\n role: impl
  • No migration of existing files (C-002)

Parallel Opportunities

  • T009 can proceed in parallel with T006-T008

Dependencies

  • Depends on WP01 (needs ActorIdentity dataclass)

Risks & Mitigations

  • Frontmatter parsing regex may not handle multi-line YAML mappings → use ruamel.yaml parser for agent field

Work Package WP03: CLI Identity Flags (Priority: P1)

Goal: Add --tool, --model, --profile, --role individual flags alongside the existing --agent compound flag to all relevant CLI commands, with mutual exclusion validation. Independent Test: Run move-task with --agent claude:opus-4:implementer:implementer, verify JSONL event has structured actor. Run with --tool claude --model opus-4, verify identical result. Run with both --agent and --tool, verify clear error. Prompt: tasks/WP03-cli-identity-flags.md Requirement Refs: FR-003, FR-010, SC-004

Included Subtasks

  • ✅ T010 Create parse_agent_identity() function in src/specify_cli/identity.py
  • ✅ T011 Add --tool/--model/--profile/--role flags to move-task command in src/specify_cli/cli/commands/agent/tasks.py
  • ✅ T012 Add --tool/--model/--profile/--role flags to implement and review commands in src/specify_cli/cli/commands/agent/workflow.py
  • ✅ T013 Wire parsed identity through emit_status_transition() actor parameter in src/specify_cli/status/emit.py
  • ✅ T014 Implement mutual exclusion validation (--agent vs individual flags raise typer.BadParameter)

Implementation Notes

  • parse_agent_identity() returns ActorIdentity | NoneNone when no identity flags provided
  • Mutual exclusion: if agent is not None and any of tool/model/profile/role is not None, raise clear error
  • emit_status_transition() currently takes actor: str — update to actor: str | ActorIdentity; coerce str to ActorIdentity.from_legacy() at the boundary
  • Ensure compound string parsing handles 1-4 parts gracefully

Parallel Opportunities

  • T011 and T012 can proceed in parallel (different files)

Dependencies

  • Depends on WP01 (ActorIdentity), WP02 (frontmatter integration)

Risks & Mitigations

  • emit.py is called from many places → make actor param accept both str and ActorIdentity for backwards compat

Work Package WP04: DoctrineCatalog Expansion (Priority: P0)

Goal: Expand DoctrineCatalog to enumerate all doctrine artifact types (tactics, styleguides, toolguides, procedures, agent profiles) so the constitution compiler can validate and select from the full inventory. Independent Test: Load a doctrine catalog from a directory containing all artifact types and verify the catalog contains the expected IDs for each new field. Verify loaded profile count matches .agent.yaml file count; loaded tactic count matches .tactic.yaml file count. Prompt: tasks/WP04-doctrine-catalog-expansion.md Requirement Refs: FR-005

Included Subtasks

  • ✅ T015 [P] Add tactics, styleguides, toolguides, procedures, profiles frozenset fields to DoctrineCatalog in src/specify_cli/constitution/catalog.py
  • ✅ T016 [P] Update load_doctrine_catalog() to call _load_yaml_id_catalog() for each new artifact type with appropriate glob patterns
  • ✅ T017 Update catalog tests in tests/specify_cli/constitution/test_catalog.py for expanded fields
  • ✅ T018 Verify _load_yaml_id_catalog() handles profiles using profile-id field from *.agent.yaml

Implementation Notes

  • Reuse existing _load_yaml_id_catalog(directory, pattern) function — it's battle-tested
  • Glob patterns: .tactic.yaml, .styleguide.yaml, .toolguide.yaml, .procedure.yaml, *.agent.yaml
  • For profiles, _load_yaml_id_catalog() extracts id field by default — profiles use profile-id, so may need to pass a id_field parameter or handle in the function
  • All new fields default to empty frozensets when directory doesn't contain those types

Parallel Opportunities

  • Track B WP04 runs in parallel with Track A WP01 (no shared dependencies)
  • T015 and T016 can proceed in parallel

Dependencies

  • None (starting package for Track B)

Risks & Mitigations

  • Profile YAML uses profile-id not id → ensure _load_yaml_id_catalog() can handle alternative ID fields

Work Package WP05: Transitive Reference Resolver (Priority: P1)

Goal: Build the transitive reference resolver that follows directive → tactic → styleguide/toolguide reference chains using DoctrineService repositories, producing a ResolvedReferenceGraph with cycle detection. Independent Test: Given a set of directive IDs where directives reference tactics and tactics reference styleguides, resolve transitively and verify the graph contains all expected artifacts. Verify cycles are detected and broken without infinite loops. Prompt: tasks/WP05-transitive-reference-resolver.md Requirement Refs: FR-006, NFR-002, SC-003

Included Subtasks

  • ✅ T019 Create ResolvedReferenceGraph frozen dataclass in src/specify_cli/constitution/reference_resolver.py
  • ✅ T020 Implement resolve_references_transitively() with DFS and visited-set cycle detection
  • ✅ T021 Handle unresolved references: collect as (type, id) pairs in unresolved field
  • ✅ T022 Follow reference patterns from DoctrineService repositories (directive tactic_refs → tactic references → styleguide/toolguide)

Implementation Notes

  • Pattern: follow doctrine/curation/engine.py:depth_first_order() for DFS with visited set
  • Algorithm: start from directive IDs → load each via doctrine_service.directives → extract tactic_refs → load tactics → extract references → load styleguides/toolguides
  • Reference structure in tactics: references: [{type: "styleguide", id: "python-style"}, ...]
  • Cycle detection: skip already-visited (type, id) pairs
  • Unresolved: when a repository .get() returns None, record (type, id) in unresolved list

Parallel Opportunities

  • T019 (dataclass definition) can proceed in parallel with T020-T022

Dependencies

  • Depends on WP04 (needs expanded DoctrineCatalog for validation)

Risks & Mitigations

  • Repository API differences between artifact types → verify each repository's get() method signature
  • Deep chains could be slow → bounded by doctrine asset count (typically <100)

Work Package WP06: Compiler Integration with DoctrineService (Priority: P2)

Goal: Inject DoctrineService into the constitution compiler to replace raw YAML scanning with typed repository queries and transitive reference resolution, while preserving the legacy fallback path. Independent Test: Compile a constitution with DoctrineService provided and verify the output includes transitively resolved references. Compile without DoctrineService and verify the fallback YAML scanning path works and emits a diagnostic warning. Prompt: tasks/WP06-compiler-doctrine-integration.md Requirement Refs: FR-007, FR-009, NFR-003, C-003, SC-005

Included Subtasks

  • ✅ T023 Add doctrine_service: DoctrineService | None = None parameter to compile_constitution()
  • ✅ T024 Implement DoctrineService-backed artifact loading (replace _index_yaml_assets when service available)
  • ✅ T025 Wire transitive resolution from reference_resolver into _build_references()
  • ✅ T026 Implement fallback path with diagnostic warning when DoctrineService is None
  • ✅ T027 Update _sanitize_catalog_selection() calls for expanded artifact types (tactics, styleguides, etc.)

Implementation Notes

  • compile_constitution() currently takes doctrine_catalog: DoctrineCatalog | None — add doctrine_service alongside it
  • When doctrine_service is not None: use resolve_references_transitively() for directive chains
  • When doctrine_service is None: retain existing _index_yaml_assets() / _load_yaml_asset() path + emit diagnostic
  • _build_references() produces ConstitutionReference objects — extend to include tactic/styleguide/toolguide references
  • Diagnostic: diagnostics.append("DoctrineService unavailable; using YAML scanning fallback")

Parallel Opportunities

  • T023 and T026 can proceed together (adding param + fallback path)

Dependencies

  • Depends on WP04 (expanded catalog), WP05 (transitive resolver)

Risks & Mitigations

  • Compiler is complex code (~300 lines) → make changes surgically, test fallback path thoroughly
  • Existing tests must pass with doctrine_service=None → ensures backwards compat

Work Package WP07: Profile-Aware Governance Compilation (Priority: P2)

Goal: Enable constitution compilation for a specific agent profile and role, producing a governance document that includes only the directives, tactics, and guides relevant to that profile, resolved transitively. Independent Test: Run spec-kitty constitution generate-for-agent --profile reviewer and verify the output includes exactly the directives and transitively resolved tactics/guides for the reviewer profile. Prompt: tasks/WP07-profile-aware-governance.md Requirement Refs: FR-008, SC-003

Included Subtasks

  • ✅ T028 Extend GovernanceResolution in src/specify_cli/constitution/resolver.py with tactics, styleguides, toolguides, profile_id, role fields
  • ✅ T029 Add agent_profile and agent_role optional fields to ConstitutionInterview in src/specify_cli/constitution/interview.py
  • ✅ T030 Implement resolve_governance_for_profile() in src/specify_cli/constitution/resolver.py
  • ✅ T031 Wire profile-aware compilation path in compile_constitution() when interview.agent_profile is set
  • ✅ T032 Create generate-for-agent CLI subcommand under spec-kitty constitution

Implementation Notes

  • resolve_governance_for_profile(): load profile → extract directive references → merge with interview selections (union, profile first) → transitive resolution → build extended GovernanceResolution
  • Profile loading: doctrine_service.agent_profiles.resolve_profile(profile_id) handles inheritance
  • Union merge: profile directives appear before interview directives in result
  • generate-for-agent subcommand: --profile <id> required, --role <role> optional
  • Graceful fallback: if DoctrineService unavailable, raise informative error (profile-aware compilation requires doctrine)

Parallel Opportunities

  • T028/T029 (dataclass extensions) can proceed in parallel with T032 (CLI subcommand scaffolding)

Dependencies

  • Depends on WP05 (transitive resolver), WP06 (compiler integration)

Risks & Mitigations

  • Profile inheritance complexity → rely on existing resolve_profile() method
  • Missing profile ID → raise ValueError with clear message

Work Package WP08: End-to-End Integration Tests (Priority: P3)

Goal: Validate both tracks work together end-to-end: structured identity flows through the full pipeline (CLI → frontmatter → event log → read), and profile-aware constitution compilation produces correct transitive output. Independent Test: Run the full integration test suite and verify all assertions pass for both tracks plus all existing regression tests. Prompt: tasks/WP08-integration-tests.md Requirement Refs: FR-010, NFR-001, NFR-004, SC-001, SC-002, SC-004, SC-005

Included Subtasks

  • ✅ T033 Integration test: structured identity through full pipeline (frontmatter → status event → JSONL → read back)
  • ✅ T034 Integration test: profile-aware constitution compilation (directives → tactics → styleguides resolved transitively)
  • ✅ T035 Integration test: CLI compound --agent flag → structured event log entry
  • ✅ T036 Regression validation: all existing constitution and status test suites pass without modification
  • ✅ T037 Integration test: mixed event log (legacy bare-string + structured identity) reduces correctly

Implementation Notes

  • Test files: tests/integration/test_structured_identity_e2e.py, tests/integration/test_profile_constitution_e2e.py
  • T033: write structured identity to WP frontmatter → read it → emit status event → read JSONL → assert all 4 fields intact
  • T034: set up doctrine assets (directives, tactics, styleguides) → compile with profile → assert output includes transitive closure
  • T035: invoke move-task programmatically with compound --agent → read JSONL event → assert structured actor
  • T036: run pytest tests/specify_cli/constitution/ tests/specify_cli/status/ -v and verify 0 failures
  • T037: create JSONL with mix of "actor": "claude" and "actor": {"tool": "claude", ...} → reduce → assert all snapshots valid

Parallel Opportunities

  • T033, T034, T035, T037 can all proceed in parallel (independent test files)

Dependencies

  • Depends on WP01, WP02, WP03, WP06, WP07 (all implementation WPs)

Risks & Mitigations

  • Flaky test data → use deterministic fixtures with known JSONL content
  • Test isolation → each test creates its own temp directory and event log

Dependency & Execution Summary

Wave 1 (parallel):   WP01 (ActorIdentity)         WP04 (Catalog expansion)
                        │                              │
Wave 2 (parallel):   WP02 (Frontmatter)            WP05 (Transitive resolver)
                        │                              │
Wave 3 (parallel):   WP03 (CLI flags)              WP06 (Compiler + DoctrineService)
                        │                              │
Wave 4:                 │                           WP07 (Profile governance)
                        │                              │
Wave 5:              WP08 (E2E tests) ←────────────────┘
  • Sequence: WP01/WP04 (parallel) → WP02/WP05 (parallel) → WP03/WP06 (parallel) → WP07 → WP08
  • Parallelization: Waves 1-3 run both tracks simultaneously (2 agents). Wave 4-5 are sequential.
  • MVP Scope: WP01 + WP02 + WP03 (Track A only — delivers structured identity without governance compilation)

Requirements Coverage Summary

Requirement IDCovered By Work Package(s)
FR-001WP01
FR-002WP01
FR-003WP03
FR-004WP02
FR-005WP04
FR-006WP05
FR-007WP06
FR-008WP07
FR-009WP06
FR-010WP03, WP08
NFR-001WP01, WP08
NFR-002WP05
NFR-003WP06
NFR-004WP01, WP02, WP08
C-001WP01
C-002WP02
C-003WP06
SC-001WP01, WP08
SC-002WP08
SC-003WP05, WP07
SC-004WP03, WP08
SC-005WP06, WP08

Subtask Index (Reference)

Subtask IDSummaryWork PackagePriorityParallel?
T001Create ActorIdentity frozen dataclassWP01P0No
T002Implement serialisation methods with validationWP01P0No
T003Modify StatusEvent.actor type to ActorIdentityWP01P0No
T004Update StatusEvent.to_dict()/from_dict()WP01P0No
T005Update _guard_actor_required() for ActorIdentityWP01P0No
T006Update frontmatter read path for structured agentWP02P1No
T007Update frontmatter write path for structured YAMLWP02P1No
T008Update WorkPackage.agent return typeWP02P1No
T009Handle unknown tool names in round-tripWP02P1Yes
T010Create parse_agent_identity() functionWP03P1No
T011Add identity flags to move-task commandWP03P1Yes
T012Add identity flags to workflow commandsWP03P1Yes
T013Wire parsed identity through emit.pyWP03P1No
T014Mutual exclusion validationWP03P1No
T015Add new frozenset fields to DoctrineCatalogWP04P0Yes
T016Update load_doctrine_catalog() for new typesWP04P0Yes
T017Update catalog tests for expanded fieldsWP04P0No
T018Handle profiles with profile-id fieldWP04P0No
T019Create ResolvedReferenceGraph dataclassWP05P1Yes
T020Implement resolve_references_transitively()WP05P1No
T021Handle unresolved referencesWP05P1No
T022Follow DoctrineService reference patternsWP05P1No
T023Add doctrine_service parameter to compilerWP06P2Yes
T024DoctrineService-backed artifact loadingWP06P2No
T025Wire transitive resolution into _build_referencesWP06P2No
T026Implement fallback path with diagnosticWP06P2Yes
T027Update _sanitize_catalog_selection for new typesWP06P2No
T028Extend GovernanceResolution fieldsWP07P2Yes
T029Add agent_profile/agent_role to ConstitutionInterviewWP07P2Yes
T030Implement resolve_governance_for_profile()WP07P2No
T031Wire profile-aware path in compilerWP07P2No
T032Create generate-for-agent CLI subcommandWP07P2Yes
T033E2E test: structured identity pipelineWP08P3Yes
T034E2E test: profile-aware compilationWP08P3Yes
T035E2E test: CLI compound flag → event logWP08P3Yes
T036Regression: existing test suites passWP08P3No
T037E2E test: mixed event log reduces correctlyWP08P3Yes

<!-- status-model:start -->

Canonical Status (Generated)

<!-- status-model:end -->

  • WP01: done
  • WP02: done
  • WP03: done
  • WP04: done
  • WP05: done
  • WP06: done
  • WP07: done
  • WP08: done