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
ActorIdentityfrozen dataclass insrc/specify_cli/identity.py - ✅ T002 Implement serialisation methods (
to_dict,from_dict,to_compact,from_compact,from_legacy) with field validation - ✅ T003 Modify
StatusEvent.actortype fromstrtoActorIdentityinsrc/specify_cli/status/models.py - ✅ T004 Update
StatusEvent.to_dict()andfrom_dict()for backwards-compatible actor serialisation - ✅ T005 Update
_guard_actor_required()insrc/specify_cli/status/transitions.pyto acceptActorIdentity
Implementation Notes
ActorIdentityis a frozen dataclass with fourstrfields:tool,model,profile,role- Compound string format:
tool:model:profile:rolewith: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()detectsstrvsdictand constructsActorIdentityaccordingly- No changes to
store.py— it delegates toto_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.pyin 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.pyto 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.agentproperty insrc/specify_cli/tasks_support.pyto returnActorIdentity | 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.yamlfor 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
ActorIdentitydataclass)
Risks & Mitigations
- Frontmatter parsing regex may not handle multi-line YAML mappings → use
ruamel.yamlparser 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 insrc/specify_cli/identity.py - ✅ T011 Add
--tool/--model/--profile/--roleflags tomove-taskcommand insrc/specify_cli/cli/commands/agent/tasks.py - ✅ T012 Add
--tool/--model/--profile/--roleflags toimplementandreviewcommands insrc/specify_cli/cli/commands/agent/workflow.py - ✅ T013 Wire parsed identity through
emit_status_transition()actor parameter insrc/specify_cli/status/emit.py - ✅ T014 Implement mutual exclusion validation (
--agentvs individual flags raisetyper.BadParameter)
Implementation Notes
parse_agent_identity()returnsActorIdentity | None—Nonewhen no identity flags provided- Mutual exclusion: if
agentis not None and any oftool/model/profile/roleis not None, raise clear error emit_status_transition()currently takesactor: str— update toactor: str | ActorIdentity; coerce str toActorIdentity.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,profilesfrozenset fields toDoctrineCataloginsrc/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.pyfor expanded fields - ✅ T018 Verify
_load_yaml_id_catalog()handles profiles usingprofile-idfield 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()extractsidfield by default — profiles useprofile-id, so may need to pass aid_fieldparameter 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-idnotid→ 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
ResolvedReferenceGraphfrozen dataclass insrc/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 inunresolvedfield - ✅ T022 Follow reference patterns from
DoctrineServicerepositories (directivetactic_refs→ tacticreferences→ 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→ extracttactic_refs→ load tactics → extractreferences→ 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 = Noneparameter tocompile_constitution() - ✅ T024 Implement DoctrineService-backed artifact loading (replace
_index_yaml_assetswhen service available) - ✅ T025 Wire transitive resolution from
reference_resolverinto_build_references() - ✅ T026 Implement fallback path with diagnostic warning when
DoctrineServiceis None - ✅ T027 Update
_sanitize_catalog_selection()calls for expanded artifact types (tactics, styleguides, etc.)
Implementation Notes
compile_constitution()currently takesdoctrine_catalog: DoctrineCatalog | None— adddoctrine_servicealongside it- When
doctrine_serviceis not None: useresolve_references_transitively()for directive chains - When
doctrine_serviceis None: retain existing_index_yaml_assets()/_load_yaml_asset()path + emit diagnostic _build_references()producesConstitutionReferenceobjects — 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
GovernanceResolutioninsrc/specify_cli/constitution/resolver.pywithtactics,styleguides,toolguides,profile_id,rolefields - ✅ T029 Add
agent_profileandagent_roleoptional fields toConstitutionInterviewinsrc/specify_cli/constitution/interview.py - ✅ T030 Implement
resolve_governance_for_profile()insrc/specify_cli/constitution/resolver.py - ✅ T031 Wire profile-aware compilation path in
compile_constitution()wheninterview.agent_profileis set - ✅ T032 Create
generate-for-agentCLI subcommand underspec-kitty constitution
Implementation Notes
resolve_governance_for_profile(): load profile → extract directive references → merge with interview selections (union, profile first) → transitive resolution → build extendedGovernanceResolution- Profile loading:
doctrine_service.agent_profiles.resolve_profile(profile_id)handles inheritance - Union merge: profile directives appear before interview directives in result
generate-for-agentsubcommand:--profile <id>required,--role <role>optional- Graceful fallback: if
DoctrineServiceunavailable, 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
--agentflag → 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/ -vand 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 ID | Covered By Work Package(s) |
|---|---|
| FR-001 | WP01 |
| FR-002 | WP01 |
| FR-003 | WP03 |
| FR-004 | WP02 |
| FR-005 | WP04 |
| FR-006 | WP05 |
| FR-007 | WP06 |
| FR-008 | WP07 |
| FR-009 | WP06 |
| FR-010 | WP03, WP08 |
| NFR-001 | WP01, WP08 |
| NFR-002 | WP05 |
| NFR-003 | WP06 |
| NFR-004 | WP01, WP02, WP08 |
| C-001 | WP01 |
| C-002 | WP02 |
| C-003 | WP06 |
| SC-001 | WP01, WP08 |
| SC-002 | WP08 |
| SC-003 | WP05, WP07 |
| SC-004 | WP03, WP08 |
| SC-005 | WP06, WP08 |
Subtask Index (Reference)
| Subtask ID | Summary | Work Package | Priority | Parallel? |
|---|---|---|---|---|
| T001 | Create ActorIdentity frozen dataclass | WP01 | P0 | No |
| T002 | Implement serialisation methods with validation | WP01 | P0 | No |
| T003 | Modify StatusEvent.actor type to ActorIdentity | WP01 | P0 | No |
| T004 | Update StatusEvent.to_dict()/from_dict() | WP01 | P0 | No |
| T005 | Update _guard_actor_required() for ActorIdentity | WP01 | P0 | No |
| T006 | Update frontmatter read path for structured agent | WP02 | P1 | No |
| T007 | Update frontmatter write path for structured YAML | WP02 | P1 | No |
| T008 | Update WorkPackage.agent return type | WP02 | P1 | No |
| T009 | Handle unknown tool names in round-trip | WP02 | P1 | Yes |
| T010 | Create parse_agent_identity() function | WP03 | P1 | No |
| T011 | Add identity flags to move-task command | WP03 | P1 | Yes |
| T012 | Add identity flags to workflow commands | WP03 | P1 | Yes |
| T013 | Wire parsed identity through emit.py | WP03 | P1 | No |
| T014 | Mutual exclusion validation | WP03 | P1 | No |
| T015 | Add new frozenset fields to DoctrineCatalog | WP04 | P0 | Yes |
| T016 | Update load_doctrine_catalog() for new types | WP04 | P0 | Yes |
| T017 | Update catalog tests for expanded fields | WP04 | P0 | No |
| T018 | Handle profiles with profile-id field | WP04 | P0 | No |
| T019 | Create ResolvedReferenceGraph dataclass | WP05 | P1 | Yes |
| T020 | Implement resolve_references_transitively() | WP05 | P1 | No |
| T021 | Handle unresolved references | WP05 | P1 | No |
| T022 | Follow DoctrineService reference patterns | WP05 | P1 | No |
| T023 | Add doctrine_service parameter to compiler | WP06 | P2 | Yes |
| T024 | DoctrineService-backed artifact loading | WP06 | P2 | No |
| T025 | Wire transitive resolution into _build_references | WP06 | P2 | No |
| T026 | Implement fallback path with diagnostic | WP06 | P2 | Yes |
| T027 | Update _sanitize_catalog_selection for new types | WP06 | P2 | No |
| T028 | Extend GovernanceResolution fields | WP07 | P2 | Yes |
| T029 | Add agent_profile/agent_role to ConstitutionInterview | WP07 | P2 | Yes |
| T030 | Implement resolve_governance_for_profile() | WP07 | P2 | No |
| T031 | Wire profile-aware path in compiler | WP07 | P2 | No |
| T032 | Create generate-for-agent CLI subcommand | WP07 | P2 | Yes |
| T033 | E2E test: structured identity pipeline | WP08 | P3 | Yes |
| T034 | E2E test: profile-aware compilation | WP08 | P3 | Yes |
| T035 | E2E test: CLI compound flag → event log | WP08 | P3 | Yes |
| T036 | Regression: existing test suites pass | WP08 | P3 | No |
| T037 | E2E test: mixed event log reduces correctly | WP08 | P3 | Yes |
<!-- 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