Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 32723c6022 | |||
| 9d05ece3f0 | |||
| 5cd5d99198 | |||
| 0c2df43a2f | |||
| c04f4eedc9 | |||
| e2aa62e597 | |||
| 50aa553cea | |||
| 5742e2c334 | |||
| 0362c192d4 | |||
| 57542dcbc1 | |||
| dda49cd821 | |||
| 4a8f842a37 | |||
| c1e2680bc6 | |||
| 93bf49479b | |||
| 1641a3165d | |||
| c5798f58e4 | |||
| 7424893758 | |||
| d220bae007 |
@@ -2,7 +2,7 @@
|
||||
# Custodian Brief — kaizen-agentic
|
||||
|
||||
**Domain:** custodian
|
||||
**Last synced:** 2026-06-16 23:04 UTC
|
||||
**Last synced:** 2026-06-18 13:14 UTC
|
||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||
|
||||
## Active Workstreams
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -45,3 +45,7 @@ htmlcov/
|
||||
|
||||
# Backup directories created by optimization scripts
|
||||
agents_backup_*/
|
||||
|
||||
# Project-scoped kaizen runtime state (ADR-002, ADR-004)
|
||||
.kaizen/agents/
|
||||
.kaizen/metrics/
|
||||
|
||||
15
.kaizen/schedule.yml
Normal file
15
.kaizen/schedule.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
# Kaizen scheduled agent execution manifest (ADR-005)
|
||||
# Engagement: coulomb-loop bootstrap — weekly cadence
|
||||
# Regulator promotes cadence per customer engagement policy (ADR-003).
|
||||
# Validate with: kaizen-agentic schedule validate
|
||||
version: '1'
|
||||
timezone: Europe/Berlin
|
||||
agents:
|
||||
coach:
|
||||
cadence: weekly
|
||||
cron: 0 9 * * 1
|
||||
enabled: true
|
||||
optimization:
|
||||
cadence: weekly
|
||||
cron: 0 10 * * 1
|
||||
enabled: true
|
||||
31
.repo-classification.yaml
Normal file
31
.repo-classification.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
# Repo classification (Repo Classification Standard v1.0).
|
||||
# First-pass agent classification (CUST-WP-0050 T02) — pending human review.
|
||||
|
||||
repo_classification:
|
||||
standard: Repo Classification Standard
|
||||
version: '1.0'
|
||||
classified_at: '2026-06-22'
|
||||
classified_by: agent
|
||||
category: tooling
|
||||
domain: agents
|
||||
secondary_domains:
|
||||
- infotech
|
||||
capability_tags:
|
||||
- orchestration
|
||||
- automation
|
||||
- coordination
|
||||
- knowledge
|
||||
- documentation
|
||||
business_stake:
|
||||
- technology
|
||||
- product
|
||||
- automation
|
||||
- people
|
||||
- intelligence
|
||||
business_mechanics:
|
||||
- intention
|
||||
- coordination
|
||||
- operation
|
||||
- adaptation
|
||||
notes: 'Digital talent-agency framework: agent personas, project memory, improvement loops,
|
||||
CLI tooling. Primary domain agents (AI-native), infotech secondary.'
|
||||
16
CHANGELOG.md
16
CHANGELOG.md
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Removed
|
||||
- **`TODO.md`** — work tracking uses `workplans/` and State Hub (ADR-001); the
|
||||
`keepaTodofile` agent remains available for other projects
|
||||
|
||||
### Added
|
||||
- **`metrics record --emit-event`** — publishes `kaizen.metrics.recorded` NATS
|
||||
envelope for activity-core event-driven definitions (optional `nats-py` via
|
||||
`pip install 'kaizen-agentic[events]'`)
|
||||
- **`schedule init --engagement`** — bootstrap presets for customer engagements
|
||||
(`--bootstrap-cadence hourly|daily|weekly`)
|
||||
- **ADR-006** — customer engagement convention (supplier/customer split, playbook)
|
||||
- **Playbook v1** — `docs/integrations/customer-engagement-playbook.md`,
|
||||
`customer-engagement-repo-layout.md`, override manifest design
|
||||
- **Event contract** — `docs/integrations/kaizen-metrics-recorded-event.md`
|
||||
- **Engagement handoff** — `docs/integrations/activity-core-handoff-engagement.md`
|
||||
|
||||
## [1.4.0] - 2026-06-18
|
||||
|
||||
### Added
|
||||
|
||||
@@ -45,7 +45,7 @@ This repository follows PythonVibes best practices:
|
||||
- `src/kaizen_agentic/` - Core framework source code
|
||||
- `agents/` - Specialized agent definitions (17+ agents)
|
||||
- `tests/` - Comprehensive test suite
|
||||
- `TODO.md` - Current development tasks (Keep a Todofile format)
|
||||
- `workplans/` - Active workstreams and tasks (ADR-001)
|
||||
- `CHANGELOG.md` - Version history (Keep a Changelog format)
|
||||
|
||||
### Making Changes
|
||||
@@ -155,15 +155,15 @@ When reporting bugs, please include:
|
||||
1. **Discuss significant changes** in an issue first
|
||||
2. **Keep PRs focused** on a single feature or fix
|
||||
3. **Write clear commit messages** following conventional commit format
|
||||
4. **Update relevant documentation** including TODO.md and CHANGELOG.md
|
||||
4. **Update relevant documentation** including workplans and CHANGELOG.md
|
||||
5. **Ensure all checks pass** including tests and linting
|
||||
6. **Respond to review feedback** promptly and constructively
|
||||
|
||||
## Agent-Assisted Development
|
||||
|
||||
This repository includes 17+ specialized agents to assist with development:
|
||||
- Use `todo-keeper` for TODO.md maintenance
|
||||
- Use `changelog-keeper` for CHANGELOG.md updates
|
||||
- Use `keepaChangelog` for CHANGELOG.md updates
|
||||
- Use `project-assistant` for workplan and session orientation
|
||||
- Use `contributing-keeper` for this file maintenance
|
||||
- See CLAUDE.md for complete agent catalog and usage
|
||||
|
||||
|
||||
219
TODO.md
219
TODO.md
@@ -1,219 +0,0 @@
|
||||
# Todofile
|
||||
|
||||
This is a "to do next" file, particularly useful to keep the human and a coding assistant in sync.
|
||||
|
||||
The format is based on [Keep a Todofile V0.0.1](https://coulomb.social/open/TodoFileGuide).
|
||||
|
||||
The structure organizes **future tasks** by their impact, just as a changelog organizes past changes by their impact.
|
||||
|
||||
***
|
||||
|
||||
## [Unreleased] - *Active Vibe-Coding State* 💡
|
||||
|
||||
Tasks in workplan: `workplans/kaizen-agentic-WP-0007-agent-authoring-doc-generation.md` (v1.4.0)
|
||||
|
||||
### Implemented (pending v1.4.0 tag)
|
||||
* **`create-agent`** — scaffold schema-valid agents
|
||||
* **`docs generate [--check]`** — idempotent CLAUDE.md Installed Agents refresh
|
||||
* **Frontmatter schema validation** in `validate`
|
||||
* **Doc-regeneration idempotency fix** + agent file rename (project-assistant)
|
||||
|
||||
### To Add (release)
|
||||
* **Tag v1.4.0** — after review
|
||||
* **activity-core implementation** — WP-0006 resolver + sync (separate repo; see handoff doc)
|
||||
|
||||
### Shipped — v1.3.0 (2026-06-17)
|
||||
* **ADR-005 + `.kaizen/schedule.yml`** — scheduled agent execution contract
|
||||
* **`kaizen-agentic schedule`** — validate, init, prepare, list
|
||||
* **activity-core definitions** — weekly coach + optimization on preselected repos
|
||||
|
||||
### Deferred / future
|
||||
* Interactive agent selection wizard (multi-step) — `create-agent` covers the
|
||||
single-agent scaffold; a guided multi-agent wizard remains future work
|
||||
* Multi-file agent packages / protocol scaffolding
|
||||
|
||||
***
|
||||
|
||||
## [1.1.0] - Community Engagement — *Shipped 2026-06-18*
|
||||
|
||||
See `CHANGELOG.md` [1.1.0] and `workplans/kaizen-agentic-WP-0001-community-engagement.md`.
|
||||
|
||||
***
|
||||
|
||||
## [COMPLETED] - *Documentation Standards Compliance - Version 0.2.0*
|
||||
|
||||
### ✅ Completed: To Add
|
||||
* **CLI interface** for agent management and execution - DONE
|
||||
- Full `kaizen-agentic` CLI with 8 commands (init, install, update, remove, list, status, validate, templates)
|
||||
- Console script entry point for global availability
|
||||
- Complete command-line interface with options and help
|
||||
* **Comprehensive tests** for optimization module (PerformanceMetrics, OptimizationLoop) - DONE
|
||||
- 24 tests covering all components with 100% pass rate
|
||||
- Test coverage for registry, installer, and core functionality
|
||||
- Automated test execution with make test/test-all targets
|
||||
* **Agent registry and installer functionality** - DONE
|
||||
- Complete AgentRegistry with categorization and dependency resolution
|
||||
- AgentInstaller with backup/rollback capabilities
|
||||
- ProjectInitializer with template-based project creation
|
||||
* **Comprehensive documentation system** - DONE
|
||||
- GETTING_STARTED.md - Complete setup and usage guide
|
||||
- AGENT_DISTRIBUTION.md - Architecture documentation
|
||||
- CLI_CHEAT_SHEET.md - Quick reference guide
|
||||
- Updated README.md with complete usage examples
|
||||
|
||||
### ✅ Completed: To Refactor
|
||||
* **Agent definitions** to follow consistent format standards - DONE
|
||||
- All 17+ agents updated to YAML frontmatter format
|
||||
- Consistent categorization system (project-management, development-process, etc.)
|
||||
- Proper dependency declarations and descriptions
|
||||
* **Makefile structure** for better user experience - DONE
|
||||
- Reorganized with consistent naming (agents-, setup-, standards- prefixes)
|
||||
- Enhanced help system with examples and clear descriptions
|
||||
- 50+ targets organized into logical sections
|
||||
* **Test execution performance** optimization - DONE
|
||||
- Fast execution with all 24 tests completing in <1 second
|
||||
- Parallel execution capabilities
|
||||
- Efficient backup/cleanup in test teardown
|
||||
|
||||
### ✅ Completed: To Fix
|
||||
* **Makefile targets** compatibility across environments - DONE
|
||||
- Cross-platform compatibility with proper shell detection
|
||||
- Virtual environment handling with fallback mechanisms
|
||||
- Error handling and recovery in setup processes
|
||||
* **Linting issues** in core modules - DONE
|
||||
- All flake8 violations resolved across entire codebase
|
||||
- PEP 8 compliance achieved
|
||||
- Consistent code formatting with black
|
||||
* **Virtual environment setup** reliability - DONE
|
||||
- Robust venv creation and activation
|
||||
- Dependency installation with upgrade handling
|
||||
- Status checking and validation
|
||||
* **CLI installation make target (agents-install-cli)** - DONE
|
||||
- Target is working properly and requires virtual environment activation as documented
|
||||
- Installation process functions correctly with proper venv setup
|
||||
* **YAML frontmatter errors in agent files** causing loading failures - DONE
|
||||
- All 16 agents now properly loading without YAML parsing errors
|
||||
- Frontmatter format standardized across all agent definitions
|
||||
* **Agent categorization issues** (agents showing as "Unknown" instead of proper categories) - DONE
|
||||
- All 16 agents now properly categorized and displaying correct categories
|
||||
- Category mapping and recognition system functioning correctly
|
||||
|
||||
### ✅ Completed: To Secure
|
||||
* **Configuration file validation** - DONE
|
||||
- Safe YAML parsing with error handling
|
||||
- Input validation for all CLI parameters
|
||||
- Backup creation before modifications
|
||||
* **Agent integrity checks** - DONE
|
||||
- Validation system for agent definitions
|
||||
- Dependency conflict detection
|
||||
- Installation verification
|
||||
|
||||
### ✅ Completed: To Remove
|
||||
* **Obsolete test fixtures** - DONE
|
||||
- Clean test directory structure
|
||||
- Removed unused imports and variables
|
||||
- Eliminated development scaffolding
|
||||
|
||||
***
|
||||
|
||||
## [COMPLETED] - *Scenario 1: Greenfield Project Excellence - Version 0.2.1*
|
||||
|
||||
### ✅ Completed: Scenario 1 Tasks
|
||||
* **Scenario 1 exploration: Establish codebase from scratch** - DONE
|
||||
- Research current onboarding experience with existing setup targets - DONE
|
||||
- Identify gaps in documentation for new project creation - DONE
|
||||
- Test and validate smooth project initialization workflows - DONE
|
||||
- Evaluate agent selection and recommendation systems for new projects - DONE
|
||||
- Document best practices for greenfield project setup - DONE
|
||||
|
||||
### ✅ Completed: Additional Scenario 1 Improvements
|
||||
* **Agent template name mappings fixed** - DONE
|
||||
- Fixed agent template name mappings to match actual agent names
|
||||
- Ensured consistency between template references and installed agents
|
||||
* **ProjectInitializer enhancements** - DONE
|
||||
- Added Makefile creation to ProjectInitializer for complete greenfield setup
|
||||
- Implemented comprehensive project scaffolding
|
||||
* **Test framework stability** - DONE
|
||||
- Fixed all failing tests for agent framework updates
|
||||
- Achieved 100% test pass rate across all components
|
||||
* **Documentation and tutorial creation** - DONE
|
||||
- Updated documentation with correct agent names
|
||||
- Created HelloWorld tutorial for new users
|
||||
- Comprehensive onboarding experience established
|
||||
|
||||
### ✅ Completed: Production Readiness
|
||||
* **Scenario 1 state achievement** - DONE
|
||||
- Scenario 1 (establish codebase from scratch) is now excellent and production-ready
|
||||
- Complete end-to-end workflow for greenfield projects
|
||||
- Robust agent selection and project initialization
|
||||
|
||||
***
|
||||
|
||||
## [COMPLETED] - *Production Release with Release Management - Version 1.0.0*
|
||||
|
||||
### ✅ Completed: Release Management System
|
||||
* **Complete release management system** with agent-releaseManager - DONE
|
||||
- 6 structured make targets for complete release workflow
|
||||
- `release-check` - Validate release readiness with comprehensive checklist
|
||||
- `release-prepare` - Build packages and prepare for publication
|
||||
- `release-test` - Test publication workflow using TestPyPI
|
||||
- `release-publish` - Publish to production PyPI with safety checks
|
||||
- `release-finalize` - Post-release tasks (tags, GitHub releases, documentation)
|
||||
- `release-rollback` - Emergency rollback procedures and guidance
|
||||
* **Local package installation capability** - DONE
|
||||
- `make install-local` target for PyPI-equivalent testing
|
||||
- Local package building and installation workflow
|
||||
- Integration testing with locally built packages
|
||||
* **Documentation updates for installation options** - DONE
|
||||
- Updated documentation to reflect all installation methods
|
||||
- PyPI installation guidance and local development setup
|
||||
- Complete user onboarding documentation
|
||||
* **Package distribution readiness** - DONE
|
||||
- Full package ready for PyPI publication
|
||||
- All agents included in package data distribution
|
||||
- Console script entry point for global CLI availability
|
||||
- Version 1.0.0 production release achieved
|
||||
|
||||
***
|
||||
|
||||
## [COMPLETED] - *Scenario 2: Existing Project Integration Excellence - Version 0.2.2*
|
||||
|
||||
### ✅ Completed: Scenario 2 Tasks
|
||||
* **Scenario 2 exploration: Integration with existing projects having agents** - DONE
|
||||
- Research detection of existing agent systems in projects - DONE
|
||||
- Design conflict resolution strategies for overlapping agent functionality - DONE
|
||||
- Create migration paths for replacing outdated project-specific agents - DONE
|
||||
- Develop extension mechanisms for project-specific kaizen agent customizations - DONE
|
||||
- Define integration patterns that respect existing project structure - DONE
|
||||
- Build tooling for safe agent system transitions - DONE
|
||||
|
||||
### ✅ Completed: Major Components Built
|
||||
* **Detection system (detection.py)** for analyzing existing agent systems - DONE
|
||||
- Comprehensive agent system detection across multiple formats
|
||||
- Analysis of conflicts and overlap identification
|
||||
- Integration readiness assessment
|
||||
* **Migration framework (migration.py)** with 5 migration strategies - DONE
|
||||
- Replace strategy for complete agent replacement
|
||||
- Merge strategy for combining functionalities
|
||||
- Extend strategy for adding new capabilities
|
||||
- Coexist strategy for parallel operation
|
||||
- Archive strategy for deprecation handling
|
||||
* **Extension system (extensions.py)** for project-specific customizations - DONE
|
||||
- Plugin-based architecture for agent customization
|
||||
- Dynamic loading and configuration management
|
||||
- Safe extension isolation and validation
|
||||
* **Complete CLI integration** with detect, migrate, and extensions commands - DONE
|
||||
- kaizen-agentic detect command for system analysis
|
||||
- kaizen-agentic migrate command with strategy selection
|
||||
- kaizen-agentic extensions command for customization management
|
||||
* **Integration patterns documentation** with 5 proven scenarios - DONE
|
||||
- Established patterns for common integration scenarios
|
||||
- Best practices for safe agent system transitions
|
||||
- Comprehensive documentation and examples
|
||||
|
||||
### ✅ Completed: Production Readiness
|
||||
* **Scenario 2 state achievement** - DONE
|
||||
- Scenario 2 (existing project integration) is now excellent and production-ready
|
||||
- Complete detection, migration, and extension capabilities
|
||||
- Safe and reliable agent system transitions
|
||||
- Both Scenario 1 (greenfield) and Scenario 2 (existing projects) are production-ready
|
||||
@@ -112,6 +112,8 @@ Session-close template: `docs/templates/session-close-protocol.md`
|
||||
# Opt this repo into fleet scheduling
|
||||
kaizen-agentic schedule init # coach + optimization weekly
|
||||
kaizen-agentic schedule init --timezone UTC # override timezone
|
||||
kaizen-agentic schedule init --engagement coulomb-loop \
|
||||
--agents coach,optimization --bootstrap-cadence hourly
|
||||
kaizen-agentic schedule validate # schema + agent-name checks
|
||||
kaizen-agentic schedule list # enabled entries (--all incl. disabled)
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ invoke kaizen-agentic CLI commands.
|
||||
|------------|---------|-------------|
|
||||
| [weekly-metrics-optimize](integrations/activity-definitions/weekly-metrics-optimize.md) | Cron Mon 08:00 | `metrics optimize` |
|
||||
| [post-install-metrics-scaffold](integrations/activity-definitions/post-install-metrics-scaffold.md) | `kaizen.agent.installed` | `memory init` validation |
|
||||
| [low-success-rate-review](integrations/activity-definitions/low-success-rate-review.md) | `kaizen.metrics.recorded` | `metrics show` + `optimize` |
|
||||
| [low-success-rate-review](integrations/activity-definitions/low-success-rate-review.md) | `kaizen.metrics.recorded` | `metrics record --emit-event` |
|
||||
|
||||
**Activation handoff (activity-core owners):**
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ Memory-enabled agents record per-session outcomes at close:
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1>
|
||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1> --emit-event
|
||||
kaizen-agentic metrics optimize [agent]
|
||||
kaizen-agentic memory brief <agent> # includes Performance Summary
|
||||
```
|
||||
@@ -45,4 +46,4 @@ fleet layers above satisfy INTENT's "measurable agents" requirement without tele
|
||||
## Feedback loop
|
||||
|
||||
User experience feedback uses [FEEDBACK.md](FEEDBACK.md) and Gitea issue templates —
|
||||
separate from execution metrics.
|
||||
separate from execution metrics.
|
||||
|
||||
@@ -105,7 +105,7 @@ Assembles, from **local `.kaizen/` state only** (offline-safe):
|
||||
- The agent prompt (`agents/agent-<name>.md`, installed or packaged).
|
||||
- Project memory (`.kaizen/agents/<name>/memory.md`) when present.
|
||||
- Metrics summary (`.kaizen/metrics/<name>/summary.json`) when present.
|
||||
- Repo pointers (`SCOPE.md`, `TODO.md`) when present.
|
||||
- Repo pointers (`SCOPE.md`, `workplans/`) when present.
|
||||
- Suggested session-close commands (`metrics record`, memory update).
|
||||
|
||||
Output is `markdown` (default) or `json` (`--format json`) so activity-core can
|
||||
@@ -115,6 +115,7 @@ embed it in a task `description` or a runner can parse it.
|
||||
|
||||
```
|
||||
kaizen-agentic schedule init [--target PATH] [--timezone TZ] [--force]
|
||||
kaizen-agentic schedule init --engagement <slug> [--agents A,B] [--bootstrap-cadence hourly|daily|weekly]
|
||||
kaizen-agentic schedule validate [--target PATH]
|
||||
kaizen-agentic schedule list [--target PATH] [--all]
|
||||
kaizen-agentic schedule prepare <agent> [--target PATH] [--format markdown|json]
|
||||
|
||||
74
docs/adr/ADR-006-customer-engagement-convention.md
Normal file
74
docs/adr/ADR-006-customer-engagement-convention.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# ADR-006: Customer Engagement Convention
|
||||
|
||||
**Status:** accepted
|
||||
**Date:** 2026-06-18
|
||||
**Deciders:** kaizen-agentic maintainers (supplier)
|
||||
**Customer reference:** coulomb-loop / LOOP-WP-0000
|
||||
|
||||
## Context
|
||||
|
||||
Coulomb's `coulomb-loop` engagement introduced a **customer/supplier split**:
|
||||
the customer repo holds contracts and operations; `kaizen-agentic` ships agents
|
||||
and CLI; target repos hold `.kaizen/` runtime state. We need a durable convention
|
||||
so the second customer engagement reuses ≤50% of coulomb-loop setup effort.
|
||||
|
||||
## Decision
|
||||
|
||||
### 1. Repo roles
|
||||
|
||||
| Role | Owns |
|
||||
|------|------|
|
||||
| **Customer engagement repo** | INTENT, workplans, ADRs, rosters, ActivityDefinition copies, loop health |
|
||||
| **Supplier (kaizen-agentic)** | Agent prompts, CLI, integration specs, playbook, supplier workplans |
|
||||
| **Target repos** | `.kaizen/schedule.yml`, `agents/`, `metrics/`, memory |
|
||||
| **activity-core** | Cron/event orchestration, resolvers, task creation |
|
||||
|
||||
### 2. `.kaizen/` placement
|
||||
|
||||
Project-scoped kaizen state **never** lives in the customer engagement repo.
|
||||
It lives only in repos where agents execute (fleet pilots).
|
||||
|
||||
### 3. Supplier CLI engagement mode
|
||||
|
||||
```bash
|
||||
kaizen-agentic schedule init --engagement <customer-slug> \
|
||||
--agents coach,optimization --bootstrap-cadence hourly
|
||||
```
|
||||
|
||||
Writes bootstrap schedule with engagement comment and hourly cron presets.
|
||||
See [customer-engagement-playbook.md](../integrations/customer-engagement-playbook.md).
|
||||
|
||||
### 4. Event emission (opt-in)
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1> --emit-event
|
||||
```
|
||||
|
||||
Publishes `kaizen.metrics.recorded` for activity-core event definitions.
|
||||
Default off for backward compatibility.
|
||||
|
||||
### 5. Playbook lifecycle
|
||||
|
||||
1. **Bootstrap** — customer LOOP-WP-0000; supplier KAIZEN-WP-0008 Part 1
|
||||
2. **Smoke** — hourly E2E on pilot roster (LOOP-WP-0001)
|
||||
3. **Automate** — emit-event, activity-core definitions enabled incrementally
|
||||
4. **Generalize** — supplier-notes → playbook v1; ADR-006 + layout docs
|
||||
5. **Second customer** — copy layout; swap roster and definitions
|
||||
|
||||
### 6. ActivityDefinition ownership
|
||||
|
||||
Bootstrap uses **customer-owned copies** (coulomb DEC-003 option A).
|
||||
Hybrid override manifest (option C) is design-only — see
|
||||
[activity-definition-override-manifest.md](../integrations/activity-definition-override-manifest.md).
|
||||
|
||||
## Consequences
|
||||
|
||||
- New engagements follow [customer-engagement-repo-layout.md](../integrations/customer-engagement-repo-layout.md)
|
||||
- Supplier friction is logged in customer `loops/*/supplier-notes.md` and absorbed into playbook
|
||||
- activity-core handoff requirements are documented per engagement wave
|
||||
|
||||
## Related
|
||||
|
||||
- coulomb-loop `docs/adr/ADR-002-customer-supplier-boundary.md`
|
||||
- [ADR-005](ADR-005-scheduled-agent-execution.md) — schedule contract
|
||||
- [KAIZEN-WP-0008](../../workplans/kaizen-agentic-WP-0008-coulomb-loop-supplier-engagement.md)
|
||||
109
docs/integrations/activity-core-handoff-engagement.md
Normal file
109
docs/integrations/activity-core-handoff-engagement.md
Normal file
@@ -0,0 +1,109 @@
|
||||
# activity-core Handoff — Customer Engagement Bootstrap (WP-0008)
|
||||
|
||||
Coordination requirements for **activity-core** to support coulomb-loop-style
|
||||
customer engagements after kaizen-agentic ships `schedule init --engagement` and
|
||||
`metrics record --emit-event`.
|
||||
|
||||
Open as an activity-core issue titled *"Engagement bootstrap: event-payload +
|
||||
bootstrap cadence alignment"*.
|
||||
|
||||
## Supplier capabilities (kaizen-agentic — done)
|
||||
|
||||
- [x] `schedule init --engagement <slug> --agents coach,optimization --bootstrap-cadence hourly`
|
||||
- [x] `metrics record --emit-event` → `activity.kaizen.metrics.recorded`
|
||||
- [x] Resolver contract: [discover-kaizen-scheduled-repos.md](discover-kaizen-scheduled-repos.md)
|
||||
|
||||
## activity-core requirements
|
||||
|
||||
### R1 — `event-payload` context resolver (blocks LOOP-WP-0002 event path)
|
||||
|
||||
**Problem:** Event-triggered definitions bind `context.metrics` via
|
||||
`type: event-payload`, but `resolve_context` treats unknown types as `{}`.
|
||||
|
||||
**Requirement:** Register a resolver (or inline handler) that, when
|
||||
`event_envelope_json` is present, binds `EventEnvelope.attributes` to the
|
||||
`bind_to` key (stripping `context.` prefix).
|
||||
|
||||
```yaml
|
||||
context_sources:
|
||||
- type: event-payload
|
||||
bind_to: context.metrics
|
||||
```
|
||||
|
||||
Expected snapshot after resolve:
|
||||
|
||||
```python
|
||||
{"metrics": {"agent": "coach", "project": "kaizen-agentic", "summary": {...}}}
|
||||
```
|
||||
|
||||
**Acceptance:** Manual NATS publish of a [kaizen-metrics-recorded-event](kaizen-metrics-recorded-event.md)
|
||||
envelope triggers `low-success-rate-review` and evaluates
|
||||
`context.metrics.summary.success_rate` without binding `{}`.
|
||||
|
||||
### R2 — Bootstrap cadence enum vs cron (blocks mis-tuned filters)
|
||||
|
||||
**Problem:** Engagement bootstrap writes **hourly cron** expressions but keeps
|
||||
`cadence: daily` in `.kaizen/schedule.yml` (coulomb-loop ADR-003 convention).
|
||||
Customer ActivityDefinitions filter `discover_kaizen_scheduled_repos` with
|
||||
`cadence: daily`.
|
||||
|
||||
**Requirement:** Do **not** require `cadence: hourly` in schedule.yml (invalid
|
||||
in ADR-005 schema). Continue matching on the `cadence` enum field; rely on
|
||||
per-repo `cron` overrides for hourly firing.
|
||||
|
||||
**Acceptance:** Resolver with `cadence: daily` returns pilot repos whose
|
||||
`schedule.yml` contains `cron: "15 * * * *"` / `"30 * * * *"`.
|
||||
|
||||
### R3 — ActivityDefinition cron alignment
|
||||
|
||||
Customer coulomb definitions offset from repo schedule crons:
|
||||
|
||||
| Definition | activity-core cron | Repo schedule cron |
|
||||
|------------|-------------------|-------------------|
|
||||
| hourly-metrics-optimize | `0 * * * *` | (metrics path; no schedule) |
|
||||
| hourly-coach-orientation | `15 * * * *` | coach `15 * * * *` |
|
||||
| hourly-optimization-review | `30 * * * *` | optimization `30 * * * *` |
|
||||
|
||||
**Requirement:** Keep definition crons and repo schedule crons in sync during
|
||||
bootstrap. Document in customer handoff that `schedule init --engagement`
|
||||
presets must match enabled ActivityDefinition offsets.
|
||||
|
||||
### R4 — Event router registration
|
||||
|
||||
**Requirement:** Ensure `kaizen.metrics.recorded` is an allowed event type in
|
||||
`event_type_registry` (if registry is enforced on publish path).
|
||||
|
||||
**Acceptance:** Event router dispatches to enabled `low-success-rate-review`
|
||||
definitions when envelope `type` matches.
|
||||
|
||||
### R5 — Engagement roster path (optional v1.1)
|
||||
|
||||
Customer rosters live in the engagement repo (e.g.
|
||||
`coulomb-loop/loops/kaizen-stack/roster.yaml`), not in target repos.
|
||||
|
||||
**Suggestion:** Support optional resolver param `engagement_slug` that looks up
|
||||
roster path from a hub field or env `KAIZEN_ENGAGEMENT_ROSTER` so definitions
|
||||
need not hard-code absolute paths.
|
||||
|
||||
## Smoke sequence (end-to-end)
|
||||
|
||||
```bash
|
||||
# Target repo (supplier runs on pilot)
|
||||
kaizen-agentic schedule init --engagement coulomb-loop \
|
||||
--agents coach,optimization --bootstrap-cadence hourly --force
|
||||
kaizen-agentic schedule validate
|
||||
kaizen-agentic memory init coach && kaizen-agentic memory init optimization
|
||||
|
||||
# Record + emit (requires nats-py)
|
||||
kaizen-agentic metrics record coach --success --time 60 --quality 0.9 --emit-event
|
||||
|
||||
# activity-core
|
||||
# 1. Resolver dry-run: discover_kaizen_scheduled_repos + cadence=daily → 6 runs
|
||||
# 2. Event dry-run: publish sample envelope → low-success-rate-review tasks
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [customer engagement playbook](customer-engagement-playbook.md) (supplier)
|
||||
- coulomb-loop `docs/integrations/activity-core-handoff.md` (customer)
|
||||
- [KAIZEN-WP-0008](../../workplans/kaizen-agentic-WP-0008-coulomb-loop-supplier-engagement.md)
|
||||
@@ -21,22 +21,35 @@ execution (WP-0006)"* and track the boxes there.
|
||||
|
||||
## What activity-core must do
|
||||
|
||||
- [ ] **Implement resolver** `discover_kaizen_scheduled_repos` per the spec
|
||||
(hub roster ∩ repos with valid `.kaizen/schedule.yml`).
|
||||
- [ ] **Add resolver unit tests** using the four fixtures in the spec.
|
||||
- [ ] **Copy definitions** from `docs/integrations/activity-definitions/`
|
||||
- [x] **Implement resolver** `discover_kaizen_scheduled_repos` per the spec
|
||||
(`activity_core/context_resolvers/kaizen.py`).
|
||||
- [x] **Add resolver unit tests** — `tests/test_kaizen_context_resolver.py` (7 passed).
|
||||
- [ ] **Copy custodian weekly definitions** from `docs/integrations/activity-definitions/`
|
||||
(`weekly-coach-orientation.md`, `weekly-optimization-review.md`) into the
|
||||
activity-core catalog path (per ACT-ADR-002).
|
||||
- [ ] **Register** each definition slug in the activity-core index.
|
||||
- [ ] **Run** `make sync-activity-definitions` in activity-core.
|
||||
- [ ] **Wire cron** triggers (Mon 09:00 / 10:00 Europe/Berlin) to the resolver.
|
||||
- [ ] **Smoke test** against a pilot repo (see below) with the definition still
|
||||
`enabled: false` (dry-run task creation).
|
||||
- [ ] **Enable gradually** — flip one definition to `enabled: true` in staging
|
||||
after the smoke test passes.
|
||||
- [ ] **Verify runner prerequisites** — `kaizen-agentic` on PATH and the Gitea
|
||||
PyPI extra index if the runner installs from registry (see
|
||||
[PACKAGE_RELEASE.md](../PACKAGE_RELEASE.md)).
|
||||
activity-core catalog path (per ACT-ADR-002). *Deferred: coulomb-loop engagement
|
||||
ships customer-owned daily bootstrap definitions instead (LOOP-WP-0001).*
|
||||
- [ ] **Register** weekly definition slugs in the activity-core index (when custodian
|
||||
fleet moves off coulomb engagement copies).
|
||||
- [x] **Run** `make sync-activity-definitions` — coulomb-loop definitions synced
|
||||
(`coulomb-hourly-coach-orientation`, `coulomb-hourly-optimization-review`, etc.).
|
||||
- [x] **Wire cron** triggers — daily stabilize chain on pilot roster (08:00–10:00).
|
||||
- [x] **Smoke test** — resolver dry-run returns 6 `scheduled_runs` on coulomb roster;
|
||||
`schedule prepare coach` succeeds on `kaizen-agentic` and `the-custodian`.
|
||||
- [x] **Enable gradually** — coulomb bootstrap definitions `enabled: true` on pilot.
|
||||
- [x] **Verify runner prerequisites** — `kaizen-agentic` on PATH; `KAIZEN_RUNNER_HOST`
|
||||
set on worker (`bnt-lap001`).
|
||||
|
||||
### Supplier closure evidence (2026-06-18)
|
||||
|
||||
| Check | Result |
|
||||
|-------|--------|
|
||||
| Resolver dry-run (coulomb roster, `cadence: daily`) | 6 runs across 3 pilot repos |
|
||||
| `tests/test_kaizen_context_resolver.py` | 7 passed |
|
||||
| `schedule validate` + `prepare coach` | OK on `kaizen-agentic`, `the-custodian`, `activity-core` |
|
||||
| Live definitions using resolver | `f234bb1a` coach, `097fc027` optimization (enabled) |
|
||||
|
||||
**Remaining activity-core item:** optional custodian-fleet weekly templates (`enabled: false`
|
||||
in kaizen-agentic drafts) when engagement moves beyond coulomb-loop bootstrap copies.
|
||||
|
||||
## State Hub team (the-custodian)
|
||||
|
||||
|
||||
72
docs/integrations/activity-definition-override-manifest.md
Normal file
72
docs/integrations/activity-definition-override-manifest.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# ActivityDefinition Override Manifest (design)
|
||||
|
||||
**Status:** design-only (DEC-003 option C future)
|
||||
**Implements:** KAIZEN-WP-0008 T08
|
||||
**Runtime:** none in v1 — customer-owned copies (option A) during bootstrap
|
||||
|
||||
## Problem
|
||||
|
||||
Customer repos copy supplier ActivityDefinitions and tune cron, labels, and
|
||||
`enabled` flags. When supplier templates change, manual merge is error-prone.
|
||||
A manifest declares **source + overrides** without duplicating full definition bodies.
|
||||
|
||||
## Proposed file
|
||||
|
||||
`activity-definitions/manifest.yaml` in the customer repo:
|
||||
|
||||
```yaml
|
||||
version: "1"
|
||||
supplier_repo: kaizen-agentic
|
||||
supplier_path: docs/integrations/activity-definitions
|
||||
engagement: coulomb-loop
|
||||
|
||||
definitions:
|
||||
- supplier_id: kaizen-weekly-coach-orientation
|
||||
customer_id: coulomb-hourly-coach-orientation
|
||||
source: ../../kaizen-agentic/docs/integrations/activity-definitions/weekly-coach-orientation.md
|
||||
overrides:
|
||||
id: coulomb-hourly-coach-orientation
|
||||
name: Hourly Kaizen Coach Orientation (coulomb-loop bootstrap)
|
||||
owner: coulomb-loop
|
||||
governance: coulomb_social
|
||||
enabled: true
|
||||
trigger:
|
||||
cron_expression: "15 * * * *"
|
||||
context_sources:
|
||||
- params:
|
||||
roster: /home/worsch/coulomb-loop/loops/kaizen-stack/roster.yaml
|
||||
cadence: daily
|
||||
|
||||
- supplier_id: kaizen-low-success-rate-review
|
||||
customer_id: coulomb-low-success-rate-review
|
||||
overrides:
|
||||
enabled: false
|
||||
owner: coulomb-loop
|
||||
```
|
||||
|
||||
## Merge rules (future tooling)
|
||||
|
||||
1. Parse supplier definition markdown (frontmatter + body)
|
||||
2. Deep-merge `overrides` (customer wins on conflict)
|
||||
3. Write customer copy to `activity-definitions/<customer_id>.md`
|
||||
4. `sync_activity_definitions` reads customer copies only (unchanged today)
|
||||
|
||||
## Ownership
|
||||
|
||||
| Layer | Owner |
|
||||
|-------|-------|
|
||||
| Manifest schema | kaizen-agentic (supplier ADR/extension) |
|
||||
| Manifest instance | customer repo |
|
||||
| Merge CLI | TBD — `coulomb-loop` or `activity-core` |
|
||||
| Sync runtime | activity-core (unchanged) |
|
||||
|
||||
## Bootstrap path (today)
|
||||
|
||||
Use **option A**: hand-copy definitions, edit cron/labels, track drift in
|
||||
`loops/kaizen-stack/supplier-notes.md`. Revisit manifest when a second customer
|
||||
engagement starts.
|
||||
|
||||
## Related
|
||||
|
||||
- coulomb-loop `docs/decisions/DEC-003-activity-definition-ownership.md`
|
||||
- [customer-engagement-playbook.md](customer-engagement-playbook.md)
|
||||
@@ -35,9 +35,11 @@ action:
|
||||
|
||||
**Threshold:** 0.8 success rate, minimum 5 executions (avoids noise on early pilots).
|
||||
|
||||
**CLI mapping:** Event emitter is future work; manual check today:
|
||||
**CLI mapping:** `kaizen-agentic metrics record <agent> --emit-event` after each
|
||||
append (see [kaizen-metrics-recorded-event.md](../kaizen-metrics-recorded-event.md)).
|
||||
Manual check today:
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics show <agent> # inspect summary.success_rate
|
||||
kaizen-agentic metrics optimize <agent>
|
||||
```
|
||||
```
|
||||
|
||||
@@ -33,15 +33,15 @@ returns one `scheduled_run` per `(repo, agent)`; this definition selects the
|
||||
id: run-weekly-coach
|
||||
for_each: context.scheduled_runs
|
||||
bind_as: r
|
||||
condition: 'r.agent == "coach" and r.enabled == true'
|
||||
condition: 'context.r.agent == "coach" and context.r.enabled'
|
||||
action:
|
||||
task_template: "Weekly coach orientation: {{r.repo}}"
|
||||
task_template: "Weekly coach orientation: {context.r.repo}"
|
||||
description: |
|
||||
{{r.prepare_command}}
|
||||
{context.r.prepare_command}
|
||||
Then load agents/agent-coach.md in a coding-agent session, paste the
|
||||
prepared bundle, and follow the coach synthesis. At session close:
|
||||
kaizen-agentic metrics record coach --success --time <s> --quality <0-1>
|
||||
target_repo: "{{r.repo}}"
|
||||
target_repo: context.r.repo
|
||||
priority: medium
|
||||
labels: ["kaizen", "agent-run", "coach", "scheduled", "automated"]
|
||||
```
|
||||
|
||||
@@ -27,13 +27,13 @@ Invokes the kaizen-agentic optimizer CLI per project.
|
||||
id: run-weekly-optimizer
|
||||
for_each: context.projects
|
||||
bind_as: p
|
||||
condition: 'p.has_metrics == true'
|
||||
condition: 'context.p.has_metrics'
|
||||
action:
|
||||
task_template: "Run kaizen metrics optimize on {{p.repo}}"
|
||||
task_template: "Run kaizen metrics optimize on {context.p.repo}"
|
||||
description: |
|
||||
cd {{p.root}} && kaizen-agentic metrics optimize
|
||||
cd {context.p.root} && kaizen-agentic metrics optimize
|
||||
Optional: kaizen-agentic metrics publish (when artifact-store configured)
|
||||
target_repo: "{{p.repo}}"
|
||||
target_repo: context.p.repo
|
||||
priority: medium
|
||||
labels: ["kaizen", "metrics", "optimizer", "automated"]
|
||||
```
|
||||
@@ -41,4 +41,4 @@ action:
|
||||
**Activation:** sync this definition into activity-core via `make sync-activity-definitions`
|
||||
after enabling the shell resolver for `discover_kaizen_projects`.
|
||||
|
||||
**CLI mapping:** `kaizen-agentic metrics optimize` (no agent filter = all agents with metrics).
|
||||
**CLI mapping:** `kaizen-agentic metrics optimize` (no agent filter = all agents with metrics).
|
||||
|
||||
@@ -30,16 +30,16 @@ review is evidence-backed.
|
||||
id: run-weekly-optimization
|
||||
for_each: context.scheduled_runs
|
||||
bind_as: r
|
||||
condition: 'r.agent == "optimization" and r.enabled == true'
|
||||
condition: 'context.r.agent == "optimization" and context.r.enabled'
|
||||
action:
|
||||
task_template: "Weekly optimization review: {{r.repo}}"
|
||||
task_template: "Weekly optimization review: {context.r.repo}"
|
||||
description: |
|
||||
{{r.prepare_command}}
|
||||
{context.r.prepare_command}
|
||||
kaizen-agentic metrics optimize # refresh evidence
|
||||
Then load agents/agent-optimization.md, paste the prepared bundle plus the
|
||||
optimizer report, and act on recommendations. At session close:
|
||||
kaizen-agentic metrics record optimization --success --time <s> --quality <0-1>
|
||||
target_repo: "{{r.repo}}"
|
||||
target_repo: context.r.repo
|
||||
priority: medium
|
||||
labels: ["kaizen", "agent-run", "optimization", "scheduled", "automated"]
|
||||
```
|
||||
|
||||
98
docs/integrations/customer-engagement-playbook.md
Normal file
98
docs/integrations/customer-engagement-playbook.md
Normal file
@@ -0,0 +1,98 @@
|
||||
# Customer Engagement Playbook v1 (supplier)
|
||||
|
||||
How kaizen-agentic supports a **customer engagement repo** (reference:
|
||||
[coulomb-loop](https://gitea.coulomb.social/coulomb/coulomb-loop)). Second engagements
|
||||
should copy [customer-engagement-repo-layout.md](customer-engagement-repo-layout.md)
|
||||
and complete the checklist in one session.
|
||||
|
||||
## Roles
|
||||
|
||||
| Repo | Role |
|
||||
|------|------|
|
||||
| Customer (coulomb-loop) | Roster, ActivityDefinition copies, cadence policy, loop health |
|
||||
| Supplier (kaizen-agentic) | Agents, CLI, integration contracts, this playbook |
|
||||
| Target repos | `.kaizen/` state (schedule, memory, metrics) |
|
||||
| activity-core | Cron + event orchestration, task creation |
|
||||
|
||||
## Bootstrap checklist
|
||||
|
||||
### 1. Customer repo
|
||||
|
||||
- Register engagement in state-hub (`register_project.sh`)
|
||||
- Write `INTENT.md`, `SCOPE.md`, LOOP-WP-0000–0004 workplans
|
||||
- Accept ADRs and DEC-* decisions; run `fix-consistency REPO=<customer>`
|
||||
- Copy ActivityDefinitions to `activity-definitions/` (DEC-003 option A)
|
||||
- Register reuse-surface capability (LOOP-WP-0000 T08)
|
||||
- Enable definitions incrementally: metrics → coach → optimization
|
||||
|
||||
### 2. Target repos (per pilot)
|
||||
|
||||
```bash
|
||||
kaizen-agentic schedule init --engagement <customer-slug> \
|
||||
--agents coach,optimization --bootstrap-cadence hourly
|
||||
kaizen-agentic schedule validate
|
||||
kaizen-agentic memory init coach
|
||||
kaizen-agentic memory init optimization
|
||||
```
|
||||
|
||||
Hourly bootstrap uses `cadence: daily` with hourly `cron` overrides — see
|
||||
[activity-core-handoff-engagement.md](activity-core-handoff-engagement.md) R2.
|
||||
|
||||
### 3. Session close (each agent run)
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1>
|
||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1> --emit-event
|
||||
```
|
||||
|
||||
Requires `pip install 'kaizen-agentic[events]'` for `--emit-event`.
|
||||
|
||||
### 4. activity-core
|
||||
|
||||
- Sync definitions from customer `activity-definitions/`
|
||||
- Implement handoff requirements in [activity-core-handoff-engagement.md](activity-core-handoff-engagement.md)
|
||||
- Manual trigger smoke before enabling hourly crons
|
||||
|
||||
## Lessons from coulomb-loop (supplier-notes absorbed)
|
||||
|
||||
| Observation | Resolution (shipped) |
|
||||
|-------------|----------------------|
|
||||
| `schedule init` weekly defaults unsuitable for bootstrap | `schedule init --engagement --bootstrap-cadence hourly` (KAIZEN-WP-0008 T04) |
|
||||
| No metrics event for LOOP-WP-0002 | `metrics record --emit-event` (T03) |
|
||||
| `metrics optimize` with 0 records confusing during bootstrap | Expected — document in bootstrap log; optimizer needs ≥10 records for recommendations |
|
||||
| ActivityDefinition drift from supplier templates | Customer copies (DEC-003 A); hybrid manifest design for v2 ([override manifest](activity-definition-override-manifest.md)) |
|
||||
| Rotation saturation signals | Future: `metrics rotation-signals` CLI (ADR-004 follow-on) |
|
||||
|
||||
## Cadence promotion
|
||||
|
||||
Customer regulator (LOOP-WP-0004) approves promotion. Use atomic promote to
|
||||
align all three layers (cadence.yml, activity-definitions, fleet schedule.yml,
|
||||
activity-core sync):
|
||||
|
||||
```bash
|
||||
kaizen-agentic schedule promote \
|
||||
--engagement-repo /path/to/customer-loop \
|
||||
--engagement <slug> \
|
||||
--to-phase operate \
|
||||
--activity-core /path/to/activity-core
|
||||
```
|
||||
|
||||
Dry-run: `--dry-run`. Repair fleet drift after a partial promotion: `--fleet-only`.
|
||||
|
||||
Legacy per-layer commands still work:
|
||||
|
||||
```bash
|
||||
kaizen-agentic schedule init --engagement <slug> --bootstrap-cadence daily --force
|
||||
kaizen-agentic schedule init --engagement <slug> --bootstrap-cadence weekly --force
|
||||
```
|
||||
|
||||
## Reference implementation
|
||||
|
||||
- Customer: [coulomb-loop INTENT](https://gitea.coulomb.social/coulomb/coulomb-loop/src/branch/main/INTENT.md)
|
||||
- Supplier workplan: [KAIZEN-WP-0008](../../workplans/kaizen-agentic-WP-0008-coulomb-loop-supplier-engagement.md)
|
||||
- Convention: [ADR-006](../adr/ADR-006-customer-engagement-convention.md)
|
||||
|
||||
## Related
|
||||
|
||||
- [INTEGRATION_PATTERNS.md](../INTEGRATION_PATTERNS.md) Pattern 2
|
||||
- [activity-core-handoff-wp0006.md](activity-core-handoff-wp0006.md)
|
||||
65
docs/integrations/customer-engagement-repo-layout.md
Normal file
65
docs/integrations/customer-engagement-repo-layout.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Customer Engagement Repo Layout
|
||||
|
||||
Contract for a **customer engagement repository** that orchestrates kaizen
|
||||
improvement loops across a fleet roster. Reference implementation:
|
||||
[coulomb-loop](https://gitea.coulomb.social/coulomb/coulomb-loop) (`coulomb_social` domain).
|
||||
|
||||
Supplier agents and CLI live in `kaizen-agentic`. Runtime `.kaizen/` state lives
|
||||
in **target repos**, not in the customer repo.
|
||||
|
||||
## Directory tree
|
||||
|
||||
```
|
||||
customer-repo/
|
||||
├── INTENT.md # Engagement purpose, loop map, cadence policy summary
|
||||
├── SCOPE.md # In/out of scope; supplier boundaries
|
||||
├── CLAUDE.md # Session protocol + rules includes
|
||||
├── .claude/rules/ # repo-identity, architecture, workplan-convention, …
|
||||
├── workplans/
|
||||
│ ├── <PREFIX>-WP-0000-* # Bootstrap (registration, ADRs, decisions)
|
||||
│ ├── <PREFIX>-WP-0001-* # Primary improvement stack
|
||||
│ ├── <PREFIX>-WP-0002-* # Reactive quality escalation
|
||||
│ ├── <PREFIX>-WP-0003-* # Registry / orientation hygiene
|
||||
│ └── <PREFIX>-WP-0004-* # Loop regulator (cadence promotion, rotation)
|
||||
├── docs/
|
||||
│ ├── adr/ # Customer ADRs (boundary, cadence, ownership)
|
||||
│ ├── decisions/ # DEC-* proposals awaiting operator acceptance
|
||||
│ └── integrations/ # activity-core handoff, event payloads
|
||||
├── history/ # Assessments, milestone snapshots
|
||||
├── activity-definitions/ # Customer-owned copies (DEC-003 option A)
|
||||
├── loops/
|
||||
│ └── <loop-id>/
|
||||
│ ├── roster.yaml # Pilot + expansion_queue + saturated
|
||||
│ ├── bootstrap-log.md # E2E cycle evidence
|
||||
│ ├── supplier-notes.md # Friction fed back to kaizen-agentic
|
||||
│ └── rotation-policy.yml # ADR-004 diminishing-returns (optional)
|
||||
└── registry/ # reuse-surface capability index (engagement D0)
|
||||
├── indexes/capabilities.yaml
|
||||
└── capabilities/
|
||||
```
|
||||
|
||||
## Naming
|
||||
|
||||
| Artifact | coulomb-loop example |
|
||||
|----------|----------------------|
|
||||
| Workplan prefix | `LOOP-WP-NNNN` |
|
||||
| Hub topic slug | `coulomb_social` |
|
||||
| Engagement slug in schedules | `coulomb-loop` |
|
||||
| Supplier workplan | `KAIZEN-WP-0008` (kaizen-agentic) |
|
||||
|
||||
## What does not live here
|
||||
|
||||
- `agents/agent-*.md` — supplier (`kaizen-agentic`)
|
||||
- `.kaizen/schedule.yml` on customer repo — target fleet repos only
|
||||
- Temporal workers / resolvers — `activity-core`
|
||||
- state-hub service code — `the-custodian`
|
||||
|
||||
## Bootstrap sequence
|
||||
|
||||
See [customer-engagement-playbook.md](customer-engagement-playbook.md).
|
||||
|
||||
## Related
|
||||
|
||||
- coulomb-loop `docs/adr/ADR-002-customer-supplier-boundary.md`
|
||||
- [ADR-006](../adr/ADR-006-customer-engagement-convention.md)
|
||||
- [KAIZEN-WP-0008](../../workplans/kaizen-agentic-WP-0008-coulomb-loop-supplier-engagement.md)
|
||||
@@ -1,6 +1,6 @@
|
||||
# Resolver Spec: `discover_kaizen_scheduled_repos`
|
||||
|
||||
**Status:** specification — **implemented in activity-core**, not here. This doc
|
||||
**Status:** implemented in activity-core (`context_resolvers/kaizen.py`). This doc
|
||||
is the contract an activity-core implementer needs to add the context resolver
|
||||
that feeds the scheduled-agent ActivityDefinitions (ADR-005).
|
||||
|
||||
|
||||
73
docs/integrations/kaizen-metrics-recorded-event.md
Normal file
73
docs/integrations/kaizen-metrics-recorded-event.md
Normal file
@@ -0,0 +1,73 @@
|
||||
# Event Payload: `kaizen.metrics.recorded`
|
||||
|
||||
**Status:** implemented — `kaizen-agentic metrics record --emit-event`
|
||||
|
||||
**Registry:** `event-types/kaizen.metrics.recorded.md` (producer-owned, synced via
|
||||
activity-core `make sync-event-types` with `ACTIVITY_DEFINITION_DIRS` including
|
||||
this repo)
|
||||
|
||||
Emitted after a successful metrics append when `--emit-event` is set. Default
|
||||
off for backward compatibility.
|
||||
|
||||
## Subject
|
||||
|
||||
```
|
||||
activity.kaizen.metrics.recorded
|
||||
```
|
||||
|
||||
Published to NATS (activity-core `EventEnvelope` format). Consumed by
|
||||
activity-core event router and definitions such as `low-success-rate-review`.
|
||||
|
||||
## Envelope
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"type": "kaizen.metrics.recorded",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-06-18T12:00:00Z",
|
||||
"publisher": "kaizen-agentic",
|
||||
"attributes": {
|
||||
"agent": "coach",
|
||||
"project": "kaizen-agentic",
|
||||
"summary": {
|
||||
"success_rate": 0.75,
|
||||
"execution_count": 12,
|
||||
"avg_quality": 0.81
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Attribute fields
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `agent` | string | Agent name from `metrics record <agent>` |
|
||||
| `project` | string | Repo slug — `KAIZEN_PROJECT_SLUG` env or directory basename |
|
||||
| `summary.success_rate` | float | Rolling rate from `summary.json` after append |
|
||||
| `summary.execution_count` | int | Total executions |
|
||||
| `summary.avg_quality` | float | Maps from `avg_quality_score` in ADR-004 summary |
|
||||
|
||||
## CLI
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics record coach --success --time 120 --quality 0.9 --emit-event
|
||||
```
|
||||
|
||||
Requires `nats-py` (`pip install 'kaizen-agentic[events]'`). Configure broker via
|
||||
`NATS_URL` (default `nats://localhost:4222`).
|
||||
|
||||
Events are **not** emitted when append is skipped (duplicate idempotency key).
|
||||
|
||||
## Consumers
|
||||
|
||||
- **activity-core** — `trigger.type: event` with `event_type: kaizen.metrics.recorded`
|
||||
- **coulomb-loop** — `low-success-rate-review` (LOOP-WP-0002); replaces hourly
|
||||
health sweep when event path is stable
|
||||
|
||||
## Related
|
||||
|
||||
- [low-success-rate-review](activity-definitions/low-success-rate-review.md)
|
||||
- [INTEGRATION_PATTERNS.md Pattern 2](../INTEGRATION_PATTERNS.md)
|
||||
- coulomb-loop `loops/quality-escalation/event-payload.md` (customer contract)
|
||||
@@ -35,7 +35,7 @@ NATS/event-bus subject, sibling to the existing `kaizen.metrics.recorded` and
|
||||
"agent_prompt_found": true,
|
||||
"has_memory": true,
|
||||
"has_metrics": true,
|
||||
"pointers": ["scope", "todo"],
|
||||
"pointers": ["scope", "workplans"],
|
||||
"bytes": 8421
|
||||
},
|
||||
"session_close": [
|
||||
@@ -60,7 +60,7 @@ NATS/event-bus subject, sibling to the existing `kaizen.metrics.recorded` and
|
||||
| `bundle.agent_prompt_found` | bool | Mirrors `schedule prepare --format json` |
|
||||
| `bundle.has_memory` | bool | Memory file present |
|
||||
| `bundle.has_metrics` | bool | Metrics summary present |
|
||||
| `bundle.pointers` | string[] | Repo pointer labels found (`scope`, `todo`) |
|
||||
| `bundle.pointers` | string[] | Repo pointer labels found (`scope`, `workplans`) |
|
||||
| `bundle.bytes` | int | Rendered bundle size |
|
||||
| `session_close` | string[] | Suggested close commands |
|
||||
|
||||
|
||||
@@ -70,11 +70,18 @@ the distribution) may appear in a schedule.
|
||||
kaizen-agentic schedule init # defaults: coach + optimization weekly
|
||||
kaizen-agentic schedule init --timezone UTC # override timezone
|
||||
kaizen-agentic schedule init --force # overwrite existing
|
||||
kaizen-agentic schedule init --engagement coulomb-loop \
|
||||
--agents coach,optimization --bootstrap-cadence hourly
|
||||
```
|
||||
|
||||
The default scaffold enables `coach` and `optimization` weekly and declares
|
||||
`tdd-workflow` monthly but **disabled** (operator opts in deliberately).
|
||||
|
||||
**Engagement bootstrap** (`--engagement`) writes customer-loop presets: hourly
|
||||
bootstrap uses `cadence: daily` with hourly `cron` overrides (coach `:15`,
|
||||
optimization `:30`). See
|
||||
[customer-engagement-playbook.md](customer-engagement-playbook.md).
|
||||
|
||||
## Listing
|
||||
|
||||
```bash
|
||||
|
||||
65
event-types/kaizen.metrics.recorded.md
Normal file
65
event-types/kaizen.metrics.recorded.md
Normal file
@@ -0,0 +1,65 @@
|
||||
---
|
||||
type_id: kaizen.metrics.recorded
|
||||
version: "1.0"
|
||||
publisher: kaizen-agentic
|
||||
governance: publisher-declared
|
||||
status: active
|
||||
---
|
||||
|
||||
# kaizen.metrics.recorded
|
||||
|
||||
## Intent
|
||||
|
||||
Emitted when a project records agent execution metrics via
|
||||
`kaizen-agentic metrics record --emit-event`. Signals that rolling performance
|
||||
summary changed and may trigger quality-escalation ActivityDefinitions.
|
||||
|
||||
**Owning repo:** `kaizen-agentic` — schema changes require supplier review.
|
||||
|
||||
## When Published
|
||||
|
||||
- CLI: `kaizen-agentic metrics record <agent> --emit-event` after successful append
|
||||
- Not emitted when append is skipped (duplicate idempotency key)
|
||||
- NATS subject: `activity.kaizen.metrics.recorded`
|
||||
|
||||
## Attributes
|
||||
|
||||
| Name | Type | Required | Description |
|
||||
|---|---|---|---|
|
||||
| agent | string | yes | Agent name from `metrics record <agent>` |
|
||||
| project | string | yes | Repo slug (`KAIZEN_PROJECT_SLUG` or directory basename) |
|
||||
| summary.success_rate | float | yes | Rolling success rate from `summary.json` after append |
|
||||
| summary.execution_count | integer | yes | Total execution count |
|
||||
| summary.avg_quality | float | no | Average quality score (maps from `avg_quality_score`) |
|
||||
|
||||
## Example Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"type": "kaizen.metrics.recorded",
|
||||
"version": "1.0",
|
||||
"timestamp": "2026-06-18T12:00:00Z",
|
||||
"publisher": "kaizen-agentic",
|
||||
"attributes": {
|
||||
"agent": "coach",
|
||||
"project": "kaizen-agentic",
|
||||
"summary": {
|
||||
"success_rate": 0.75,
|
||||
"execution_count": 12,
|
||||
"avg_quality": 0.81
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Consumer Notes
|
||||
|
||||
- **activity-core**: `event-payload` resolver binds `attributes` → `context.metrics`
|
||||
for rules such as `flag-low-success-rate`
|
||||
- **coulomb-loop**: `low-success-rate-review` (LOOP-WP-0002) — primary reactive path
|
||||
|
||||
## Related
|
||||
|
||||
- `docs/integrations/kaizen-metrics-recorded-event.md`
|
||||
- `coulomb-loop/loops/quality-escalation/event-payload.md`
|
||||
@@ -32,6 +32,9 @@ dependencies = [
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
events = [
|
||||
"nats-py>=2.6.0",
|
||||
]
|
||||
dev = [
|
||||
"pytest>=6.0.0",
|
||||
"pytest-cov>=4.0.0",
|
||||
|
||||
@@ -15,12 +15,19 @@ from .integrations.artifact_store import (
|
||||
default_api_url,
|
||||
publish_optimizer_evidence,
|
||||
)
|
||||
from .integrations.event_bus import (
|
||||
build_metrics_recorded_envelope,
|
||||
publish_metrics_recorded_event,
|
||||
resolve_project_slug,
|
||||
)
|
||||
from .integrations.helix import HelixCorrelationAdapter, enrich_helix_correlation
|
||||
from .metrics import MetricsStore, OptimizerStore, performance_summary_markdown
|
||||
from .optimization import OptimizationLoop, MIN_SAMPLES_FOR_RECOMMENDATIONS
|
||||
from .engagement_promote import promote_engagement
|
||||
from .schedule import (
|
||||
ScheduleError,
|
||||
default_schedule_yaml,
|
||||
engagement_schedule_yaml,
|
||||
load_schedule,
|
||||
schedule_path,
|
||||
validate_schedule,
|
||||
@@ -1069,6 +1076,11 @@ def metrics():
|
||||
@click.option(
|
||||
"--json", "json_input", is_flag=True, help="Read full record JSON from stdin"
|
||||
)
|
||||
@click.option(
|
||||
"--emit-event",
|
||||
is_flag=True,
|
||||
help="Publish kaizen.metrics.recorded to NATS (requires nats-py)",
|
||||
)
|
||||
def metrics_record(
|
||||
agent_name: str,
|
||||
target: str,
|
||||
@@ -1079,6 +1091,7 @@ def metrics_record(
|
||||
session_id: Optional[str],
|
||||
idempotency_key: Optional[str],
|
||||
json_input: bool,
|
||||
emit_event: bool,
|
||||
):
|
||||
"""Append one execution record for an agent."""
|
||||
store = MetricsStore(_project_root(target), agent_name)
|
||||
@@ -1109,6 +1122,21 @@ def metrics_record(
|
||||
|
||||
if store.append(payload, idempotency_key=idempotency_key):
|
||||
click.echo(f"Recorded metrics for '{agent_name}'")
|
||||
if emit_event:
|
||||
summary = store.read_summary() or store.write_summary()
|
||||
envelope = build_metrics_recorded_envelope(
|
||||
agent=agent_name,
|
||||
project=resolve_project_slug(store.project_root),
|
||||
summary=summary,
|
||||
)
|
||||
try:
|
||||
subject = publish_metrics_recorded_event(envelope)
|
||||
except RuntimeError as exc:
|
||||
click.echo(f"Error: {exc}", err=True)
|
||||
sys.exit(1)
|
||||
click.echo(
|
||||
f"Emitted kaizen.metrics.recorded for '{agent_name}' → {subject}"
|
||||
)
|
||||
else:
|
||||
click.echo(
|
||||
f"Skipped duplicate record for '{agent_name}' (idempotency key exists)"
|
||||
@@ -1429,8 +1457,39 @@ def schedule_validate(target: str):
|
||||
"--timezone", default="Europe/Berlin", show_default=True, help="Schedule timezone"
|
||||
)
|
||||
@click.option("--force", is_flag=True, help="Overwrite an existing schedule.yml")
|
||||
def schedule_init(target: str, timezone: str, force: bool):
|
||||
"""Scaffold a default .kaizen/schedule.yml (coach + optimization weekly)."""
|
||||
@click.option(
|
||||
"--engagement",
|
||||
default=None,
|
||||
help="Customer engagement slug (bootstrap schedule for target repos)",
|
||||
)
|
||||
@click.option(
|
||||
"--agents",
|
||||
default=None,
|
||||
help="Comma-separated agents for --engagement (default: coach,optimization)",
|
||||
)
|
||||
@click.option(
|
||||
"--bootstrap-cadence",
|
||||
type=click.Choice(["hourly", "daily", "weekly"]),
|
||||
default="hourly",
|
||||
show_default=True,
|
||||
help="Cadence preset for --engagement (hourly uses daily enum + hourly cron)",
|
||||
)
|
||||
def schedule_init(
|
||||
target: str,
|
||||
timezone: str,
|
||||
force: bool,
|
||||
engagement: Optional[str],
|
||||
agents: Optional[str],
|
||||
bootstrap_cadence: str,
|
||||
):
|
||||
"""Scaffold .kaizen/schedule.yml (weekly default or engagement bootstrap)."""
|
||||
if (agents or bootstrap_cadence != "hourly") and not engagement:
|
||||
click.echo(
|
||||
"Error: --agents and --bootstrap-cadence require --engagement",
|
||||
err=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
path = schedule_path(_project_root(target))
|
||||
|
||||
if path.exists() and not force:
|
||||
@@ -1438,12 +1497,143 @@ def schedule_init(target: str, timezone: str, force: bool):
|
||||
click.echo(" Use --force to overwrite.")
|
||||
return
|
||||
|
||||
if engagement:
|
||||
agent_list = (
|
||||
[item.strip() for item in agents.split(",") if item.strip()]
|
||||
if agents
|
||||
else None
|
||||
)
|
||||
known_agents = _get_registry().agent_names()
|
||||
if agent_list:
|
||||
unknown = [name for name in agent_list if name not in known_agents]
|
||||
if unknown:
|
||||
click.echo(
|
||||
f"Error: unknown agent(s) for engagement schedule: {', '.join(unknown)}",
|
||||
err=True,
|
||||
)
|
||||
sys.exit(1)
|
||||
try:
|
||||
yaml_text = engagement_schedule_yaml(
|
||||
engagement,
|
||||
agents=agent_list,
|
||||
bootstrap_cadence=bootstrap_cadence,
|
||||
timezone=timezone,
|
||||
)
|
||||
except ScheduleError as exc:
|
||||
click.echo(f"Error: {exc}", err=True)
|
||||
sys.exit(1)
|
||||
else:
|
||||
yaml_text = default_schedule_yaml(timezone=timezone)
|
||||
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(default_schedule_yaml(timezone=timezone), encoding="utf-8")
|
||||
path.write_text(yaml_text, encoding="utf-8")
|
||||
click.echo(f"Initialized schedule: {path}")
|
||||
if engagement:
|
||||
click.echo(
|
||||
f" Engagement: {engagement} (bootstrap-cadence={bootstrap_cadence})"
|
||||
)
|
||||
click.echo(" Validate with: kaizen-agentic schedule validate")
|
||||
|
||||
|
||||
@schedule.command("promote")
|
||||
@click.option(
|
||||
"--engagement-repo",
|
||||
"-e",
|
||||
required=True,
|
||||
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
||||
help="Customer engagement repo (e.g. coulomb-loop)",
|
||||
)
|
||||
@click.option(
|
||||
"--engagement",
|
||||
default="coulomb-loop",
|
||||
show_default=True,
|
||||
help="Engagement slug written into fleet schedule.yml headers",
|
||||
)
|
||||
@click.option(
|
||||
"--to-phase",
|
||||
type=click.Choice(["stabilize", "operate"]),
|
||||
default=None,
|
||||
help="Target phase (default: next phase after current)",
|
||||
)
|
||||
@click.option("--loop", default=None, help="Promote a single loop id only")
|
||||
@click.option("--dry-run", is_flag=True, help="Print planned actions without writing")
|
||||
@click.option("--skip-cadence", is_flag=True, help="Skip loops/*/cadence.yml updates")
|
||||
@click.option(
|
||||
"--skip-definitions", is_flag=True, help="Skip activity-definitions transforms"
|
||||
)
|
||||
@click.option("--skip-fleet", is_flag=True, help="Skip .kaizen/schedule.yml on roster")
|
||||
@click.option("--skip-sync", is_flag=True, help="Skip activity-core definition sync")
|
||||
@click.option(
|
||||
"--fleet-only",
|
||||
is_flag=True,
|
||||
help="Only update fleet schedule.yml (+ sync unless --skip-sync)",
|
||||
)
|
||||
@click.option(
|
||||
"--activity-core",
|
||||
type=click.Path(exists=True, file_okay=False, path_type=Path),
|
||||
default=None,
|
||||
help="activity-core repo root (or ACTIVITY_CORE_ROOT env)",
|
||||
)
|
||||
def schedule_promote(
|
||||
engagement_repo: Path,
|
||||
engagement: str,
|
||||
to_phase: Optional[str],
|
||||
loop: Optional[str],
|
||||
dry_run: bool,
|
||||
skip_cadence: bool,
|
||||
skip_definitions: bool,
|
||||
skip_fleet: bool,
|
||||
skip_sync: bool,
|
||||
fleet_only: bool,
|
||||
activity_core: Optional[Path],
|
||||
):
|
||||
"""Atomically promote cadence across cadence.yml, definitions, fleet, and sync."""
|
||||
if fleet_only:
|
||||
skip_cadence = True
|
||||
skip_definitions = True
|
||||
if to_phase is None:
|
||||
to_phase = "operate"
|
||||
|
||||
result = promote_engagement(
|
||||
engagement_repo,
|
||||
engagement_slug=engagement,
|
||||
to_phase=to_phase,
|
||||
loop=loop,
|
||||
dry_run=dry_run,
|
||||
skip_cadence=skip_cadence,
|
||||
skip_definitions=skip_definitions,
|
||||
skip_fleet=skip_fleet,
|
||||
skip_sync=skip_sync,
|
||||
activity_core_root=activity_core,
|
||||
)
|
||||
|
||||
if dry_run:
|
||||
click.echo("Dry run — planned actions:")
|
||||
else:
|
||||
click.echo("Promotion complete — actions:")
|
||||
|
||||
by_layer: dict[str, list[str]] = {}
|
||||
for action in result.actions:
|
||||
by_layer.setdefault(action.layer, []).append(action.description)
|
||||
|
||||
for layer in ("cadence", "definitions", "fleet", "sync"):
|
||||
items = by_layer.get(layer)
|
||||
if items:
|
||||
click.echo(f"\n[{layer}]")
|
||||
for item in items:
|
||||
click.echo(f" • {item}")
|
||||
|
||||
if result.errors:
|
||||
click.echo("\nWarnings / errors:", err=True)
|
||||
for err in result.errors:
|
||||
click.echo(f" ! {err}", err=True)
|
||||
if not result.actions:
|
||||
sys.exit(1)
|
||||
|
||||
if not result.actions and not result.errors:
|
||||
click.echo("Nothing to do — layers already aligned.")
|
||||
|
||||
|
||||
@schedule.command("list")
|
||||
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||
@click.option("--all", "show_all", is_flag=True, help="Include disabled entries")
|
||||
@@ -1514,10 +1704,12 @@ def _build_prepare_bundle(agent_name: str, project_root: Path) -> dict:
|
||||
metrics_summary = metrics_store.write_summary()
|
||||
|
||||
pointers = {}
|
||||
for label, filename in (("scope", "SCOPE.md"), ("todo", "TODO.md")):
|
||||
candidate = project_root / filename
|
||||
if candidate.exists():
|
||||
pointers[label] = str(candidate)
|
||||
scope_path = project_root / "SCOPE.md"
|
||||
if scope_path.exists():
|
||||
pointers["scope"] = str(scope_path)
|
||||
workplans_path = project_root / "workplans"
|
||||
if workplans_path.is_dir():
|
||||
pointers["workplans"] = str(workplans_path)
|
||||
|
||||
return {
|
||||
"agent": agent_name,
|
||||
@@ -1572,7 +1764,7 @@ def _render_prepare_markdown(bundle: dict) -> str:
|
||||
for label, path in pointers.items():
|
||||
lines.append(f"- {label}: {path}")
|
||||
else:
|
||||
lines.append("- (no SCOPE.md / TODO.md found)")
|
||||
lines.append("- (no SCOPE.md / workplans/ found)")
|
||||
lines.append("")
|
||||
|
||||
lines.append("## Session Close")
|
||||
|
||||
555
src/kaizen_agentic/engagement_promote.py
Normal file
555
src/kaizen_agentic/engagement_promote.py
Normal file
@@ -0,0 +1,555 @@
|
||||
"""Atomic cadence promotion across engagement contract layers (ADR-003).
|
||||
|
||||
Orchestrates three layers that must stay aligned on promotion:
|
||||
|
||||
1. Customer policy — ``loops/*/cadence.yml`` in the engagement repo
|
||||
2. Scheduler contract — ``activity-definitions/*.md`` → activity-core sync
|
||||
3. Fleet opt-in — ``.kaizen/schedule.yml`` on roster target repos
|
||||
|
||||
See coulomb-loop ADR-002 / ADR-003 and supplier customer-engagement-playbook.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import date
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable
|
||||
|
||||
import yaml
|
||||
|
||||
from .schedule import ScheduleError, engagement_schedule_yaml, schedule_path
|
||||
|
||||
PHASE_ORDER = ("bootstrap", "stabilize", "operate")
|
||||
PHASE_FILE_PREFIX = {
|
||||
"bootstrap": "hourly",
|
||||
"stabilize": "daily",
|
||||
"operate": "weekly",
|
||||
}
|
||||
CADENCE_ENUM = {
|
||||
"bootstrap": "daily", # hourly crons keep daily enum for resolver filter
|
||||
"stabilize": "daily",
|
||||
"operate": "weekly",
|
||||
}
|
||||
|
||||
LOOP_DIR_BY_ID = {
|
||||
"kaizen-improvement-stack": "kaizen-stack",
|
||||
"quality-escalation": "quality-escalation",
|
||||
"registry-hygiene": "registry-hygiene",
|
||||
"loop-regulator": "regulator",
|
||||
}
|
||||
|
||||
# Activity-definition stems per loop (without phase prefix). Crons come from
|
||||
# operate_target in cadence.yml when present, else these defaults.
|
||||
DEFAULT_OPERATE_CRONS: dict[str, dict[str, str]] = {
|
||||
"kaizen-improvement-stack": {
|
||||
"metrics-optimize": "0 8 * * 1",
|
||||
"coach-orientation": "0 9 * * 1",
|
||||
"optimization-review": "0 10 * * 1",
|
||||
},
|
||||
"registry-hygiene": {
|
||||
"registry-hygiene-sweep": "0 9 * * 1",
|
||||
},
|
||||
"quality-escalation": {
|
||||
"metrics-health-sweep": "0 6 * * 1",
|
||||
},
|
||||
"loop-regulator": {
|
||||
"loop-health-collector": "0 11 * * 1",
|
||||
},
|
||||
}
|
||||
|
||||
EVENT_DEFINITIONS = frozenset({"low-success-rate-review"})
|
||||
|
||||
|
||||
class PromoteError(Exception):
|
||||
"""Raised when promotion cannot proceed."""
|
||||
|
||||
|
||||
@dataclass
|
||||
class PromoteAction:
|
||||
layer: str
|
||||
description: str
|
||||
path: str | None = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class PromoteResult:
|
||||
actions: list[PromoteAction] = field(default_factory=list)
|
||||
errors: list[str] = field(default_factory=list)
|
||||
|
||||
@property
|
||||
def ok(self) -> bool:
|
||||
return not self.errors
|
||||
|
||||
|
||||
def _next_phase(current: str) -> str | None:
|
||||
try:
|
||||
idx = PHASE_ORDER.index(current)
|
||||
except ValueError:
|
||||
return None
|
||||
if idx + 1 >= len(PHASE_ORDER):
|
||||
return None
|
||||
return PHASE_ORDER[idx + 1]
|
||||
|
||||
|
||||
def _loop_paths(engagement_repo: Path) -> dict[str, Path]:
|
||||
loops_root = engagement_repo / "loops"
|
||||
if not loops_root.is_dir():
|
||||
raise PromoteError(f"loops/ not found under {engagement_repo}")
|
||||
mapping: dict[str, Path] = {}
|
||||
for loop_id, dirname in LOOP_DIR_BY_ID.items():
|
||||
path = loops_root / dirname / "cadence.yml"
|
||||
if path.is_file():
|
||||
mapping[loop_id] = path
|
||||
if not mapping:
|
||||
raise PromoteError(f"no loops/*/cadence.yml found under {engagement_repo}")
|
||||
return mapping
|
||||
|
||||
|
||||
def _load_yaml(path: Path) -> dict[str, Any]:
|
||||
data = yaml.safe_load(path.read_text(encoding="utf-8"))
|
||||
if not isinstance(data, dict):
|
||||
raise PromoteError(f"expected mapping in {path}")
|
||||
return data
|
||||
|
||||
|
||||
def _write_yaml(path: Path, data: dict[str, Any]) -> None:
|
||||
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
|
||||
|
||||
|
||||
def _split_markdown_frontmatter(text: str) -> tuple[dict[str, Any], str]:
|
||||
if not text.startswith("---"):
|
||||
raise PromoteError("activity definition missing YAML frontmatter")
|
||||
parts = text.split("---", 2)
|
||||
if len(parts) < 3:
|
||||
raise PromoteError("activity definition frontmatter not closed")
|
||||
meta = yaml.safe_load(parts[1])
|
||||
if not isinstance(meta, dict):
|
||||
raise PromoteError("activity definition frontmatter must be a mapping")
|
||||
body = parts[2].lstrip("\n")
|
||||
return meta, body
|
||||
|
||||
|
||||
def _render_markdown(meta: dict[str, Any], body: str) -> str:
|
||||
header = yaml.safe_dump(meta, sort_keys=False)
|
||||
return f"---\n{header}---\n\n{body}"
|
||||
|
||||
|
||||
def _roster_repos(engagement_repo: Path) -> list[tuple[str, Path, list[str]]]:
|
||||
roster_path = engagement_repo / "loops" / "kaizen-stack" / "roster.yaml"
|
||||
if not roster_path.is_file():
|
||||
return []
|
||||
data = _load_yaml(roster_path)
|
||||
active = data.get("active") or []
|
||||
repos: list[tuple[str, Path, list[str]]] = []
|
||||
for entry in active:
|
||||
if not isinstance(entry, dict):
|
||||
continue
|
||||
slug = str(entry.get("slug", ""))
|
||||
root = entry.get("root")
|
||||
agents = entry.get("agents") or ["coach", "optimization"]
|
||||
if slug and root:
|
||||
repos.append((slug, Path(str(root)), list(agents)))
|
||||
return repos
|
||||
|
||||
|
||||
def _operate_crons(loop_id: str, cadence: dict[str, Any]) -> dict[str, str]:
|
||||
target = cadence.get("operate_target")
|
||||
crons = dict(DEFAULT_OPERATE_CRONS.get(loop_id, {}))
|
||||
if not isinstance(target, dict):
|
||||
return crons
|
||||
if loop_id == "kaizen-improvement-stack":
|
||||
chain = target.get("chain")
|
||||
if isinstance(chain, dict):
|
||||
for stem, cron_key in [
|
||||
("metrics-optimize", "metrics"),
|
||||
("coach-orientation", "coach"),
|
||||
("optimization-review", "optimization"),
|
||||
]:
|
||||
if cron_key in chain:
|
||||
crons[stem] = str(chain[cron_key])
|
||||
elif loop_id == "registry-hygiene" and "cron" in target:
|
||||
crons["registry-hygiene-sweep"] = str(target["cron"])
|
||||
elif loop_id == "quality-escalation" and "sweep_cron" in target:
|
||||
crons["metrics-health-sweep"] = str(target["sweep_cron"])
|
||||
elif loop_id == "loop-regulator":
|
||||
if "collector_cron" in target:
|
||||
crons["loop-health-collector"] = str(target["collector_cron"])
|
||||
return crons
|
||||
|
||||
|
||||
def _apply_operate_target(
|
||||
cadence: dict[str, Any], loop_id: str, promoted_at: str
|
||||
) -> None:
|
||||
target = cadence.pop("operate_target", None)
|
||||
cadence["phase"] = "operate"
|
||||
cadence["promoted_at"] = promoted_at
|
||||
if not isinstance(target, dict):
|
||||
target = {}
|
||||
|
||||
if loop_id == "kaizen-improvement-stack":
|
||||
chain = target.get("chain") or DEFAULT_OPERATE_CRONS[loop_id]
|
||||
cadence["cron"] = chain.get("metrics", "0 8 * * 1")
|
||||
cadence["chain"] = {
|
||||
"metrics": chain.get("metrics", "0 8 * * 1"),
|
||||
"coach": chain.get("coach", "0 9 * * 1"),
|
||||
"optimization": chain.get("optimization", "0 10 * * 1"),
|
||||
}
|
||||
elif loop_id == "registry-hygiene":
|
||||
cadence["cron"] = target.get("cron", "0 9 * * 1")
|
||||
cadence["batch_size"] = target.get("batch_size", 2)
|
||||
cadence["domain_rotation"] = target.get("domain_rotation", "weekly")
|
||||
elif loop_id == "quality-escalation":
|
||||
cadence["cron"] = target.get("sweep_cron", "0 6 * * 1")
|
||||
cadence["sweep_fallback"] = "weekly-metrics-health-sweep"
|
||||
cadence.pop("stabilize_target", None)
|
||||
elif loop_id == "loop-regulator":
|
||||
cron = target.get("collector_cron", "0 11 * * 1")
|
||||
cadence["collector_cron"] = cron
|
||||
cadence["regulator_session_cron"] = target.get("regulator_session_cron", cron)
|
||||
|
||||
|
||||
def _transform_definition(
|
||||
path: Path,
|
||||
from_prefix: str,
|
||||
to_prefix: str,
|
||||
cron: str,
|
||||
cadence_enum: str,
|
||||
) -> Path:
|
||||
meta, body = _split_markdown_frontmatter(path.read_text(encoding="utf-8"))
|
||||
old_id = str(meta.get("id", ""))
|
||||
stem = path.stem.removeprefix(f"{from_prefix}-")
|
||||
new_id = f"coulomb-{to_prefix}-{stem.replace('-', '-')}"
|
||||
if old_id.startswith("coulomb-"):
|
||||
new_id = old_id.replace(f"coulomb-{from_prefix}-", f"coulomb-{to_prefix}-", 1)
|
||||
|
||||
meta["id"] = new_id
|
||||
if isinstance(meta.get("name"), str):
|
||||
meta["name"] = meta["name"].replace(from_prefix.title(), to_prefix.title())
|
||||
meta["name"] = re.sub(
|
||||
rf"\b{from_prefix}\b", to_prefix, meta["name"], flags=re.IGNORECASE
|
||||
)
|
||||
|
||||
trigger = meta.get("trigger")
|
||||
if isinstance(trigger, dict) and trigger.get("type") == "cron":
|
||||
trigger["cron_expression"] = cron
|
||||
|
||||
for source in meta.get("context_sources") or []:
|
||||
if isinstance(source, dict):
|
||||
params = source.get("params")
|
||||
if isinstance(params, dict) and "cadence" in params:
|
||||
params["cadence"] = cadence_enum
|
||||
|
||||
body = body.replace(f"{from_prefix}-", f"{to_prefix}-")
|
||||
body = re.sub(rf"\b{from_prefix}\b", to_prefix, body, flags=re.IGNORECASE)
|
||||
|
||||
dest = path.with_name(f"{to_prefix}-{stem}.md")
|
||||
dest.write_text(_render_markdown(meta, body), encoding="utf-8")
|
||||
if dest != path:
|
||||
path.unlink()
|
||||
return dest
|
||||
|
||||
|
||||
def _promote_definitions(
|
||||
engagement_repo: Path,
|
||||
from_phase: str,
|
||||
to_phase: str,
|
||||
loop_crons: dict[str, dict[str, str]],
|
||||
*,
|
||||
dry_run: bool,
|
||||
result: PromoteResult,
|
||||
) -> None:
|
||||
from_prefix = PHASE_FILE_PREFIX[from_phase]
|
||||
to_prefix = PHASE_FILE_PREFIX[to_phase]
|
||||
cadence_enum = CADENCE_ENUM[to_phase]
|
||||
defs_dir = engagement_repo / "activity-definitions"
|
||||
if not defs_dir.is_dir():
|
||||
raise PromoteError(f"activity-definitions/ not found under {engagement_repo}")
|
||||
|
||||
for loop_id, crons in loop_crons.items():
|
||||
for stem, cron in crons.items():
|
||||
src = defs_dir / f"{from_prefix}-{stem}.md"
|
||||
if not src.is_file():
|
||||
result.errors.append(f"missing definition file: {src}")
|
||||
continue
|
||||
dest = defs_dir / f"{to_prefix}-{stem}.md"
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="definitions",
|
||||
description=f"{src.name} → {dest.name} (cron {cron})",
|
||||
path=str(src),
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
continue
|
||||
_transform_definition(src, from_prefix, to_prefix, cron, cadence_enum)
|
||||
|
||||
# Update event-definition fallback references
|
||||
for name in EVENT_DEFINITIONS:
|
||||
path = defs_dir / f"{name}.md"
|
||||
if not path.is_file():
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8")
|
||||
new_text = text.replace(
|
||||
f"{from_prefix}-metrics-health-sweep",
|
||||
f"{to_prefix}-metrics-health-sweep",
|
||||
)
|
||||
new_text = new_text.replace(
|
||||
f"`0 6 * * *`",
|
||||
f"`{loop_crons.get('quality-escalation', {}).get('metrics-health-sweep', '0 6 * * 1')}`",
|
||||
)
|
||||
if new_text != text:
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="definitions",
|
||||
description=f"update fallback reference in {path.name}",
|
||||
path=str(path),
|
||||
)
|
||||
)
|
||||
if not dry_run:
|
||||
path.write_text(new_text, encoding="utf-8")
|
||||
|
||||
|
||||
def _promote_cadence_files(
|
||||
engagement_repo: Path,
|
||||
to_phase: str,
|
||||
*,
|
||||
loop_filter: str | None,
|
||||
dry_run: bool,
|
||||
result: PromoteResult,
|
||||
) -> dict[str, dict[str, str]]:
|
||||
if to_phase != "operate":
|
||||
raise PromoteError(f"promotion to '{to_phase}' not implemented yet")
|
||||
|
||||
promoted_at = date.today().isoformat()
|
||||
loop_crons: dict[str, dict[str, str]] = {}
|
||||
for loop_id, path in _loop_paths(engagement_repo).items():
|
||||
if loop_filter and loop_filter != loop_id:
|
||||
continue
|
||||
data = _load_yaml(path)
|
||||
current = str(data.get("phase", ""))
|
||||
if current == to_phase:
|
||||
loop_crons[loop_id] = _operate_crons(loop_id, data)
|
||||
continue
|
||||
if current != "stabilize":
|
||||
result.errors.append(
|
||||
f"{loop_id}: cannot promote from phase '{current}' to '{to_phase}'"
|
||||
)
|
||||
continue
|
||||
loop_crons[loop_id] = _operate_crons(loop_id, data)
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="cadence",
|
||||
description=f"{path.relative_to(engagement_repo)}: {current} → {to_phase}",
|
||||
path=str(path),
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
continue
|
||||
_apply_operate_target(data, loop_id, promoted_at)
|
||||
_write_yaml(path, data)
|
||||
|
||||
roster_path = engagement_repo / "loops" / "kaizen-stack" / "roster.yaml"
|
||||
if roster_path.is_file() and (
|
||||
loop_filter is None or loop_filter == "kaizen-improvement-stack"
|
||||
):
|
||||
roster = _load_yaml(roster_path)
|
||||
if roster.get("phase") != to_phase:
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="cadence",
|
||||
description=f"roster.yaml phase → {to_phase}",
|
||||
path=str(roster_path),
|
||||
)
|
||||
)
|
||||
if not dry_run:
|
||||
roster["phase"] = to_phase
|
||||
_write_yaml(roster_path, roster)
|
||||
|
||||
return loop_crons
|
||||
|
||||
|
||||
def _sync_roster_phase(
|
||||
engagement_repo: Path,
|
||||
to_phase: str,
|
||||
*,
|
||||
dry_run: bool,
|
||||
result: PromoteResult,
|
||||
) -> None:
|
||||
roster_path = engagement_repo / "loops" / "kaizen-stack" / "roster.yaml"
|
||||
if not roster_path.is_file():
|
||||
return
|
||||
roster = _load_yaml(roster_path)
|
||||
if roster.get("phase") == to_phase:
|
||||
return
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="cadence",
|
||||
description=f"roster.yaml phase → {to_phase}",
|
||||
path=str(roster_path),
|
||||
)
|
||||
)
|
||||
if not dry_run:
|
||||
roster["phase"] = to_phase
|
||||
_write_yaml(roster_path, roster)
|
||||
|
||||
|
||||
def _promote_fleet(
|
||||
engagement_repo: Path,
|
||||
engagement_slug: str,
|
||||
to_phase: str,
|
||||
*,
|
||||
dry_run: bool,
|
||||
result: PromoteResult,
|
||||
) -> None:
|
||||
if to_phase != "operate":
|
||||
return
|
||||
_sync_roster_phase(engagement_repo, to_phase, dry_run=dry_run, result=result)
|
||||
for slug, root, agents in _roster_repos(engagement_repo):
|
||||
if not root.is_dir():
|
||||
result.errors.append(f"fleet repo root missing: {root} ({slug})")
|
||||
continue
|
||||
sched = schedule_path(root)
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="fleet",
|
||||
description=f"{slug}: .kaizen/schedule.yml → weekly ({', '.join(agents)})",
|
||||
path=str(sched),
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
continue
|
||||
yaml_text = engagement_schedule_yaml(
|
||||
engagement_slug,
|
||||
agents=agents,
|
||||
bootstrap_cadence="weekly",
|
||||
)
|
||||
sched.parent.mkdir(parents=True, exist_ok=True)
|
||||
sched.write_text(yaml_text, encoding="utf-8")
|
||||
|
||||
|
||||
def _sync_activity_core(
|
||||
engagement_repo: Path,
|
||||
activity_core_root: Path | None,
|
||||
*,
|
||||
dry_run: bool,
|
||||
result: PromoteResult,
|
||||
) -> None:
|
||||
ac_root = activity_core_root or Path(os.environ.get("ACTIVITY_CORE_ROOT", ""))
|
||||
if not ac_root or not (ac_root / "src" / "activity_core").is_dir():
|
||||
result.errors.append(
|
||||
"activity-core root not found (set --activity-core or ACTIVITY_CORE_ROOT)"
|
||||
)
|
||||
return
|
||||
if not os.environ.get("ACTCORE_DB_URL"):
|
||||
result.errors.append("ACTCORE_DB_URL not set — skip sync or configure database")
|
||||
return
|
||||
|
||||
cmd = ["uv", "run", "python", "-m", "activity_core.sync_activity_definitions"]
|
||||
result.actions.append(
|
||||
PromoteAction(
|
||||
layer="sync",
|
||||
description="activity-core sync_activity_definitions",
|
||||
path=str(ac_root),
|
||||
)
|
||||
)
|
||||
if dry_run:
|
||||
return
|
||||
env = os.environ.copy()
|
||||
env["ACTIVITY_DEFINITION_DIRS"] = str(engagement_repo.resolve())
|
||||
subprocess.run(cmd, cwd=ac_root, env=env, check=True)
|
||||
|
||||
|
||||
def promote_engagement(
|
||||
engagement_repo: Path,
|
||||
*,
|
||||
engagement_slug: str = "coulomb-loop",
|
||||
to_phase: str | None = None,
|
||||
loop: str | None = None,
|
||||
dry_run: bool = False,
|
||||
skip_cadence: bool = False,
|
||||
skip_definitions: bool = False,
|
||||
skip_fleet: bool = False,
|
||||
skip_sync: bool = False,
|
||||
activity_core_root: Path | None = None,
|
||||
) -> PromoteResult:
|
||||
"""Run atomic cadence promotion (or dry-run plan)."""
|
||||
engagement_repo = engagement_repo.resolve()
|
||||
result = PromoteResult()
|
||||
|
||||
if not engagement_repo.is_dir():
|
||||
result.errors.append(f"engagement repo not found: {engagement_repo}")
|
||||
return result
|
||||
|
||||
# Resolve target phase from first loop if not specified
|
||||
if to_phase is None:
|
||||
for path in _loop_paths(engagement_repo).values():
|
||||
data = _load_yaml(path)
|
||||
nxt = _next_phase(str(data.get("phase", "bootstrap")))
|
||||
if nxt:
|
||||
to_phase = nxt
|
||||
break
|
||||
if to_phase is None:
|
||||
result.errors.append("could not infer target phase (all loops at operate?)")
|
||||
return result
|
||||
|
||||
loop_crons: dict[str, dict[str, str]] = {}
|
||||
from_phase = (
|
||||
PHASE_ORDER[PHASE_ORDER.index(to_phase) - 1]
|
||||
if to_phase in PHASE_ORDER[1:]
|
||||
else "stabilize"
|
||||
)
|
||||
|
||||
try:
|
||||
if not skip_cadence:
|
||||
loop_crons = _promote_cadence_files(
|
||||
engagement_repo,
|
||||
to_phase,
|
||||
loop_filter=loop,
|
||||
dry_run=dry_run,
|
||||
result=result,
|
||||
)
|
||||
else:
|
||||
for loop_id, path in _loop_paths(engagement_repo).items():
|
||||
if loop and loop != loop_id:
|
||||
continue
|
||||
loop_crons[loop_id] = _operate_crons(loop_id, _load_yaml(path))
|
||||
|
||||
if (
|
||||
not skip_definitions
|
||||
and from_phase in PHASE_FILE_PREFIX
|
||||
and to_phase in PHASE_FILE_PREFIX
|
||||
):
|
||||
if from_phase != to_phase:
|
||||
_promote_definitions(
|
||||
engagement_repo,
|
||||
from_phase,
|
||||
to_phase,
|
||||
loop_crons,
|
||||
dry_run=dry_run,
|
||||
result=result,
|
||||
)
|
||||
|
||||
if not skip_fleet:
|
||||
_promote_fleet(
|
||||
engagement_repo,
|
||||
engagement_slug,
|
||||
to_phase,
|
||||
dry_run=dry_run,
|
||||
result=result,
|
||||
)
|
||||
|
||||
if not skip_sync:
|
||||
_sync_activity_core(
|
||||
engagement_repo,
|
||||
activity_core_root,
|
||||
dry_run=dry_run,
|
||||
result=result,
|
||||
)
|
||||
except (PromoteError, ScheduleError, subprocess.CalledProcessError) as exc:
|
||||
result.errors.append(str(exc))
|
||||
|
||||
return result
|
||||
@@ -1,10 +1,18 @@
|
||||
"""Ecosystem integration adapters (Helix Forge, artifact-store)."""
|
||||
"""Ecosystem integration adapters (Helix Forge, artifact-store, event bus)."""
|
||||
|
||||
from .artifact_store import publish_optimizer_evidence
|
||||
from .event_bus import (
|
||||
build_metrics_recorded_envelope,
|
||||
publish_metrics_recorded_event,
|
||||
resolve_project_slug,
|
||||
)
|
||||
from .helix import HelixCorrelationAdapter, enrich_helix_correlation
|
||||
|
||||
__all__ = [
|
||||
"HelixCorrelationAdapter",
|
||||
"build_metrics_recorded_envelope",
|
||||
"enrich_helix_correlation",
|
||||
"publish_metrics_recorded_event",
|
||||
"publish_optimizer_evidence",
|
||||
"resolve_project_slug",
|
||||
]
|
||||
|
||||
95
src/kaizen_agentic/integrations/event_bus.py
Normal file
95
src/kaizen_agentic/integrations/event_bus.py
Normal file
@@ -0,0 +1,95 @@
|
||||
"""NATS event emission for activity-core integration (Pattern 2)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import os
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Mapping, Optional
|
||||
|
||||
EVENT_TYPE_METRICS_RECORDED = "kaizen.metrics.recorded"
|
||||
DEFAULT_NATS_URL = "nats://localhost:4222"
|
||||
DEFAULT_PUBLISHER = "kaizen-agentic"
|
||||
|
||||
|
||||
def resolve_project_slug(project_root: Path) -> str:
|
||||
"""Return state-hub repo slug for a project checkout."""
|
||||
override = os.environ.get("KAIZEN_PROJECT_SLUG", "").strip()
|
||||
if override:
|
||||
return override
|
||||
return Path(project_root).resolve().name
|
||||
|
||||
|
||||
def metrics_summary_for_event(summary: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
"""Map ADR-004 summary.json to the LOOP-WP-0002 event contract."""
|
||||
return {
|
||||
"success_rate": summary.get("success_rate", 0.0),
|
||||
"execution_count": summary.get("execution_count", 0),
|
||||
"avg_quality": summary.get(
|
||||
"avg_quality",
|
||||
summary.get("avg_quality_score", 0.0),
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def build_metrics_recorded_envelope(
|
||||
*,
|
||||
agent: str,
|
||||
project: str,
|
||||
summary: Mapping[str, Any],
|
||||
event_id: Optional[str] = None,
|
||||
publisher: str = DEFAULT_PUBLISHER,
|
||||
) -> Dict[str, Any]:
|
||||
"""Build an activity-core EventEnvelope dict for kaizen.metrics.recorded."""
|
||||
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
return {
|
||||
"id": event_id or str(uuid.uuid4()),
|
||||
"type": EVENT_TYPE_METRICS_RECORDED,
|
||||
"version": "1.0",
|
||||
"timestamp": timestamp,
|
||||
"publisher": publisher,
|
||||
"attributes": {
|
||||
"agent": agent,
|
||||
"project": project,
|
||||
"summary": metrics_summary_for_event(summary),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def nats_subject_for_event(event_type: str) -> str:
|
||||
"""Subject pattern used by activity-core webhook receiver and event router."""
|
||||
return f"activity.{event_type}"
|
||||
|
||||
|
||||
async def _publish_bytes(subject: str, payload: bytes, *, nats_url: str) -> None:
|
||||
try:
|
||||
import nats
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"nats-py is required for --emit-event. "
|
||||
"Install with: pip install 'kaizen-agentic[events]'"
|
||||
) from exc
|
||||
|
||||
nc = await nats.connect(nats_url)
|
||||
try:
|
||||
await nc.publish(subject, payload)
|
||||
await nc.flush()
|
||||
finally:
|
||||
await nc.close()
|
||||
|
||||
|
||||
def publish_metrics_recorded_event(
|
||||
envelope: Mapping[str, Any],
|
||||
*,
|
||||
nats_url: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Publish envelope to NATS. Returns the subject used."""
|
||||
url = (nats_url or os.environ.get("NATS_URL") or DEFAULT_NATS_URL).strip()
|
||||
event_type = str(envelope.get("type", EVENT_TYPE_METRICS_RECORDED))
|
||||
subject = nats_subject_for_event(event_type)
|
||||
payload = json.dumps(envelope, sort_keys=True).encode("utf-8")
|
||||
asyncio.run(_publish_bytes(subject, payload, nats_url=url))
|
||||
return subject
|
||||
@@ -27,6 +27,29 @@ DEFAULT_AGENTS: Dict[str, Dict[str, Any]] = {
|
||||
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
||||
}
|
||||
DEFAULT_TIMEZONE = "Europe/Berlin"
|
||||
DEFAULT_ENGAGEMENT_AGENTS = ("coach", "optimization")
|
||||
|
||||
# Bootstrap cadence presets for customer engagements (coulomb-loop ADR-003).
|
||||
# Hourly bootstrap keeps cadence enum ``daily`` so activity-core definitions
|
||||
# filtering ``cadence: daily`` still match while per-repo cron overrides fire
|
||||
# hourly (see docs/integrations/activity-core-handoff-engagement.md).
|
||||
ENGAGEMENT_CADENCE_PRESETS: Dict[str, Dict[str, Dict[str, Any]]] = {
|
||||
"hourly": {
|
||||
"coach": {"cadence": "daily", "cron": "15 * * * *", "enabled": True},
|
||||
"optimization": {"cadence": "daily", "cron": "30 * * * *", "enabled": True},
|
||||
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
||||
},
|
||||
"daily": {
|
||||
"coach": {"cadence": "daily", "cron": "0 8 * * *", "enabled": True},
|
||||
"optimization": {"cadence": "daily", "cron": "0 9 * * *", "enabled": True},
|
||||
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
||||
},
|
||||
"weekly": {
|
||||
"coach": {"cadence": "weekly", "cron": "0 9 * * 1", "enabled": True},
|
||||
"optimization": {"cadence": "weekly", "cron": "0 10 * * 1", "enabled": True},
|
||||
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ScheduleError(Exception):
|
||||
@@ -183,3 +206,56 @@ def default_schedule_yaml(timezone: str = DEFAULT_TIMEZONE) -> str:
|
||||
)
|
||||
body = yaml.safe_dump(document, sort_keys=False, default_flow_style=False)
|
||||
return header + body
|
||||
|
||||
|
||||
def engagement_schedule_yaml(
|
||||
engagement: str,
|
||||
*,
|
||||
agents: Optional[List[str]] = None,
|
||||
bootstrap_cadence: str = "hourly",
|
||||
timezone: str = DEFAULT_TIMEZONE,
|
||||
) -> str:
|
||||
"""Render a customer-engagement bootstrap schedule for `schedule init --engagement`."""
|
||||
if bootstrap_cadence not in ENGAGEMENT_CADENCE_PRESETS:
|
||||
raise ScheduleError(
|
||||
f"unsupported bootstrap cadence '{bootstrap_cadence}' "
|
||||
f"(expected one of {', '.join(ENGAGEMENT_CADENCE_PRESETS)})"
|
||||
)
|
||||
|
||||
slug = engagement.strip()
|
||||
if not slug:
|
||||
raise ScheduleError("engagement slug must not be empty")
|
||||
|
||||
selected = list(agents or DEFAULT_ENGAGEMENT_AGENTS)
|
||||
if not selected:
|
||||
raise ScheduleError(
|
||||
"at least one agent is required for engagement schedule init"
|
||||
)
|
||||
|
||||
preset = ENGAGEMENT_CADENCE_PRESETS[bootstrap_cadence]
|
||||
agent_entries: Dict[str, Dict[str, Any]] = {}
|
||||
for name in selected:
|
||||
if name not in preset:
|
||||
raise ScheduleError(
|
||||
f"agent '{name}' has no preset for bootstrap cadence '{bootstrap_cadence}'"
|
||||
)
|
||||
agent_entries[name] = dict(preset[name])
|
||||
|
||||
if bootstrap_cadence == "hourly":
|
||||
cadence_note = "hourly crons, daily cadence enum"
|
||||
else:
|
||||
cadence_note = f"{bootstrap_cadence} cadence"
|
||||
|
||||
document = {
|
||||
"version": SCHEDULE_VERSION,
|
||||
"timezone": timezone,
|
||||
"agents": agent_entries,
|
||||
}
|
||||
header = (
|
||||
"# Kaizen scheduled agent execution manifest (ADR-005)\n"
|
||||
f"# Engagement: {slug} bootstrap — {cadence_note}\n"
|
||||
"# Regulator promotes cadence per customer engagement policy (ADR-003).\n"
|
||||
"# Validate with: kaizen-agentic schedule validate\n"
|
||||
)
|
||||
body = yaml.safe_dump(document, sort_keys=False, default_flow_style=False)
|
||||
return header + body
|
||||
|
||||
161
tests/test_engagement_promote.py
Normal file
161
tests/test_engagement_promote.py
Normal file
@@ -0,0 +1,161 @@
|
||||
"""Tests for atomic engagement cadence promotion."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import yaml
|
||||
from click.testing import CliRunner
|
||||
|
||||
from kaizen_agentic.cli import cli
|
||||
from kaizen_agentic.engagement_promote import promote_engagement
|
||||
from kaizen_agentic.schedule import schedule_path
|
||||
|
||||
|
||||
def _write_cadence(
|
||||
repo: Path,
|
||||
loop_dir: str,
|
||||
loop_id: str,
|
||||
phase: str,
|
||||
*,
|
||||
with_operate_target: bool = True,
|
||||
) -> None:
|
||||
data: dict = {"loop": loop_id, "phase": phase, "regulator_approval": "approved"}
|
||||
if phase == "stabilize" and with_operate_target:
|
||||
if loop_id == "kaizen-improvement-stack":
|
||||
data["operate_target"] = {
|
||||
"phase": "operate",
|
||||
"chain": {
|
||||
"metrics": "0 8 * * 1",
|
||||
"coach": "0 9 * * 1",
|
||||
"optimization": "0 10 * * 1",
|
||||
},
|
||||
}
|
||||
elif loop_id == "registry-hygiene":
|
||||
data["operate_target"] = {
|
||||
"phase": "operate",
|
||||
"cron": "0 9 * * 1",
|
||||
"batch_size": 2,
|
||||
}
|
||||
path = repo / "loops" / loop_dir / "cadence.yml"
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
|
||||
|
||||
|
||||
def _write_definition(repo: Path, name: str, cron: str, cadence: str) -> None:
|
||||
defs_dir = repo / "activity-definitions"
|
||||
defs_dir.mkdir(parents=True, exist_ok=True)
|
||||
stem = name.removeprefix("daily-")
|
||||
content = f"""---
|
||||
id: coulomb-daily-{stem}
|
||||
name: Daily test {stem}
|
||||
enabled: true
|
||||
trigger:
|
||||
type: cron
|
||||
cron_expression: "{cron}"
|
||||
context_sources:
|
||||
- type: kaizen
|
||||
query: discover_kaizen_scheduled_repos
|
||||
params:
|
||||
cadence: {cadence}
|
||||
---
|
||||
|
||||
# Daily {stem}
|
||||
"""
|
||||
(repo / "activity-definitions" / name).write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
def _engagement_fixture(tmp_path: Path) -> Path:
|
||||
repo = tmp_path / "coulomb-loop"
|
||||
repo.mkdir()
|
||||
_write_cadence(repo, "kaizen-stack", "kaizen-improvement-stack", "stabilize")
|
||||
_write_cadence(repo, "registry-hygiene", "registry-hygiene", "stabilize")
|
||||
_write_definition(repo, "daily-coach-orientation.md", "0 9 * * *", "daily")
|
||||
_write_definition(repo, "daily-metrics-optimize.md", "0 8 * * *", "daily")
|
||||
_write_definition(repo, "daily-optimization-review.md", "0 10 * * *", "daily")
|
||||
_write_definition(repo, "daily-registry-hygiene-sweep.md", "0 7 * * *", "daily")
|
||||
|
||||
roster = {
|
||||
"version": "1",
|
||||
"loop": "kaizen-improvement-stack",
|
||||
"phase": "bootstrap",
|
||||
"active": [
|
||||
{
|
||||
"slug": "pilot-a",
|
||||
"root": str(tmp_path / "pilot-a"),
|
||||
"agents": ["coach", "optimization"],
|
||||
}
|
||||
],
|
||||
}
|
||||
roster_path = repo / "loops" / "kaizen-stack" / "roster.yaml"
|
||||
roster_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
roster_path.write_text(yaml.safe_dump(roster, sort_keys=False), encoding="utf-8")
|
||||
(tmp_path / "pilot-a").mkdir()
|
||||
return repo
|
||||
|
||||
|
||||
class TestEngagementPromote:
|
||||
def test_dry_run_lists_all_layers(self, tmp_path: Path) -> None:
|
||||
repo = _engagement_fixture(tmp_path)
|
||||
result = promote_engagement(
|
||||
repo,
|
||||
to_phase="operate",
|
||||
dry_run=True,
|
||||
skip_sync=True,
|
||||
)
|
||||
layers = {a.layer for a in result.actions}
|
||||
assert "cadence" in layers
|
||||
assert "definitions" in layers
|
||||
assert "fleet" in layers
|
||||
assert result.ok
|
||||
|
||||
def test_promote_updates_cadence_and_definitions(self, tmp_path: Path) -> None:
|
||||
repo = _engagement_fixture(tmp_path)
|
||||
result = promote_engagement(
|
||||
repo,
|
||||
to_phase="operate",
|
||||
skip_fleet=True,
|
||||
skip_sync=True,
|
||||
)
|
||||
assert result.ok
|
||||
cadence = yaml.safe_load(
|
||||
(repo / "loops" / "kaizen-stack" / "cadence.yml").read_text()
|
||||
)
|
||||
assert cadence["phase"] == "operate"
|
||||
assert "operate_target" not in cadence
|
||||
assert (repo / "activity-definitions" / "weekly-coach-orientation.md").is_file()
|
||||
assert not (
|
||||
repo / "activity-definitions" / "daily-coach-orientation.md"
|
||||
).is_file()
|
||||
|
||||
def test_fleet_only_writes_schedule(self, tmp_path: Path) -> None:
|
||||
repo = _engagement_fixture(tmp_path)
|
||||
# Pre-promote cadence so fleet-only path applies
|
||||
promote_engagement(repo, to_phase="operate", skip_fleet=True, skip_sync=True)
|
||||
result = promote_engagement(
|
||||
repo,
|
||||
to_phase="operate",
|
||||
skip_cadence=True,
|
||||
skip_definitions=True,
|
||||
skip_sync=True,
|
||||
)
|
||||
sched = schedule_path(tmp_path / "pilot-a")
|
||||
assert sched.is_file()
|
||||
assert "cadence: weekly" in sched.read_text()
|
||||
|
||||
def test_cli_fleet_only(self, tmp_path: Path) -> None:
|
||||
repo = _engagement_fixture(tmp_path)
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"schedule",
|
||||
"promote",
|
||||
"--engagement-repo",
|
||||
str(repo),
|
||||
"--fleet-only",
|
||||
"--skip-sync",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "[fleet]" in result.output
|
||||
188
tests/test_metrics_emit_event.py
Normal file
188
tests/test_metrics_emit_event.py
Normal file
@@ -0,0 +1,188 @@
|
||||
"""Tests for kaizen.metrics.recorded event emission."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from kaizen_agentic.cli import cli
|
||||
from kaizen_agentic.integrations.event_bus import (
|
||||
EVENT_TYPE_METRICS_RECORDED,
|
||||
build_metrics_recorded_envelope,
|
||||
metrics_summary_for_event,
|
||||
nats_subject_for_event,
|
||||
publish_metrics_recorded_event,
|
||||
resolve_project_slug,
|
||||
)
|
||||
from kaizen_agentic.metrics import MetricsStore
|
||||
|
||||
|
||||
def test_metrics_summary_for_event_maps_avg_quality_score() -> None:
|
||||
summary = metrics_summary_for_event(
|
||||
{
|
||||
"success_rate": 0.75,
|
||||
"execution_count": 12,
|
||||
"avg_quality_score": 0.81,
|
||||
}
|
||||
)
|
||||
assert summary == {
|
||||
"success_rate": 0.75,
|
||||
"execution_count": 12,
|
||||
"avg_quality": 0.81,
|
||||
}
|
||||
|
||||
|
||||
def test_build_metrics_recorded_envelope_shape() -> None:
|
||||
envelope = build_metrics_recorded_envelope(
|
||||
agent="coach",
|
||||
project="kaizen-agentic",
|
||||
summary={
|
||||
"success_rate": 0.9,
|
||||
"execution_count": 5,
|
||||
"avg_quality_score": 0.85,
|
||||
},
|
||||
event_id="test-event-id",
|
||||
)
|
||||
|
||||
assert envelope["id"] == "test-event-id"
|
||||
assert envelope["type"] == EVENT_TYPE_METRICS_RECORDED
|
||||
assert envelope["publisher"] == "kaizen-agentic"
|
||||
assert envelope["attributes"] == {
|
||||
"agent": "coach",
|
||||
"project": "kaizen-agentic",
|
||||
"summary": {
|
||||
"success_rate": 0.9,
|
||||
"execution_count": 5,
|
||||
"avg_quality": 0.85,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def test_nats_subject_for_event() -> None:
|
||||
assert nats_subject_for_event("kaizen.metrics.recorded") == (
|
||||
"activity.kaizen.metrics.recorded"
|
||||
)
|
||||
|
||||
|
||||
def test_resolve_project_slug_prefers_env(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
monkeypatch.setenv("KAIZEN_PROJECT_SLUG", "custom-slug")
|
||||
assert resolve_project_slug(tmp_path / "some-dir") == "custom-slug"
|
||||
|
||||
|
||||
def test_resolve_project_slug_falls_back_to_directory_name(tmp_path: Path) -> None:
|
||||
project = tmp_path / "kaizen-agentic"
|
||||
project.mkdir()
|
||||
assert resolve_project_slug(project) == "kaizen-agentic"
|
||||
|
||||
|
||||
def test_publish_metrics_recorded_event_uses_activity_subject(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
published: dict[str, object] = {}
|
||||
|
||||
async def fake_publish(subject: str, payload: bytes, *, nats_url: str) -> None:
|
||||
published["subject"] = subject
|
||||
published["payload"] = payload
|
||||
published["url"] = nats_url
|
||||
|
||||
monkeypatch.setattr(
|
||||
"kaizen_agentic.integrations.event_bus._publish_bytes",
|
||||
fake_publish,
|
||||
)
|
||||
|
||||
envelope = build_metrics_recorded_envelope(
|
||||
agent="coach",
|
||||
project="activity-core",
|
||||
summary={"success_rate": 1.0, "execution_count": 1, "avg_quality_score": 1.0},
|
||||
event_id="evt-1",
|
||||
)
|
||||
subject = publish_metrics_recorded_event(
|
||||
envelope, nats_url="nats://broker.test:4222"
|
||||
)
|
||||
|
||||
assert subject == "activity.kaizen.metrics.recorded"
|
||||
assert published["subject"] == "activity.kaizen.metrics.recorded"
|
||||
body = json.loads(published["payload"].decode())
|
||||
assert body["attributes"]["project"] == "activity-core"
|
||||
|
||||
|
||||
def test_metrics_record_emit_event_after_append(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
emitted: list[dict] = []
|
||||
|
||||
def capture(envelope, *, nats_url=None):
|
||||
emitted.append(dict(envelope))
|
||||
return "activity.kaizen.metrics.recorded"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"kaizen_agentic.cli.publish_metrics_recorded_event",
|
||||
capture,
|
||||
)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"metrics",
|
||||
"record",
|
||||
"coach",
|
||||
"--target",
|
||||
str(tmp_path),
|
||||
"--success",
|
||||
"--time",
|
||||
"120",
|
||||
"--quality",
|
||||
"0.9",
|
||||
"--emit-event",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0, result.output
|
||||
assert "Recorded metrics for 'coach'" in result.output
|
||||
assert "Emitted kaizen.metrics.recorded" in result.output
|
||||
assert len(emitted) == 1
|
||||
assert emitted[0]["attributes"]["agent"] == "coach"
|
||||
assert emitted[0]["attributes"]["project"] == tmp_path.name
|
||||
assert emitted[0]["attributes"]["summary"]["execution_count"] == 1
|
||||
|
||||
store = MetricsStore(tmp_path, "coach")
|
||||
assert store.read_summary() is not None
|
||||
|
||||
|
||||
def test_metrics_record_skips_emit_on_idempotency_duplicate(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
emitted: list[dict] = []
|
||||
|
||||
def capture(envelope, *, nats_url=None):
|
||||
emitted.append(dict(envelope))
|
||||
return "activity.kaizen.metrics.recorded"
|
||||
|
||||
monkeypatch.setattr(
|
||||
"kaizen_agentic.cli.publish_metrics_recorded_event",
|
||||
capture,
|
||||
)
|
||||
|
||||
runner = CliRunner()
|
||||
common = [
|
||||
"metrics",
|
||||
"record",
|
||||
"coach",
|
||||
"--target",
|
||||
str(tmp_path),
|
||||
"--success",
|
||||
"--emit-event",
|
||||
"--idempotency-key",
|
||||
"session-1",
|
||||
]
|
||||
assert runner.invoke(cli, common).exit_code == 0
|
||||
assert runner.invoke(cli, common).exit_code == 0
|
||||
assert len(emitted) == 1
|
||||
@@ -6,11 +6,13 @@ import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
from click.testing import CliRunner
|
||||
|
||||
from kaizen_agentic.cli import cli
|
||||
from kaizen_agentic.schedule import (
|
||||
ScheduleError,
|
||||
engagement_schedule_yaml,
|
||||
parse_schedule,
|
||||
schedule_path,
|
||||
validate_schedule,
|
||||
@@ -30,6 +32,20 @@ def project_dir(tmp_path: Path) -> Path:
|
||||
|
||||
|
||||
class TestScheduleModule:
|
||||
def test_engagement_schedule_yaml_hourly_preset(self):
|
||||
text = engagement_schedule_yaml(
|
||||
"coulomb-loop",
|
||||
agents=["coach", "optimization"],
|
||||
bootstrap_cadence="hourly",
|
||||
)
|
||||
assert "Engagement: coulomb-loop bootstrap" in text
|
||||
body = "\n".join(line for line in text.splitlines() if not line.startswith("#"))
|
||||
schedule = parse_schedule(yaml.safe_load(body))
|
||||
coach = schedule.entry_for("coach")
|
||||
assert coach is not None
|
||||
assert coach.cadence == "daily"
|
||||
assert coach.cron == "15 * * * *"
|
||||
|
||||
def test_parse_requires_version(self):
|
||||
with pytest.raises(ScheduleError):
|
||||
parse_schedule({"agents": {}})
|
||||
@@ -67,6 +83,67 @@ class TestScheduleCli:
|
||||
assert path.exists()
|
||||
assert "coach" in path.read_text()
|
||||
|
||||
def test_engagement_init_hourly_bootstrap(
|
||||
self, runner: CliRunner, project_dir: Path
|
||||
):
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"schedule",
|
||||
"init",
|
||||
"--target",
|
||||
str(project_dir),
|
||||
"--engagement",
|
||||
"coulomb-loop",
|
||||
"--agents",
|
||||
"coach,optimization",
|
||||
"--bootstrap-cadence",
|
||||
"hourly",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0, result.output
|
||||
text = schedule_path(project_dir).read_text()
|
||||
assert "Engagement: coulomb-loop bootstrap" in text
|
||||
assert "cron: 15 * * * *" in text
|
||||
assert "cadence: daily" in text
|
||||
assert "Engagement: coulomb-loop" in result.output
|
||||
|
||||
def test_engagement_init_validates_unknown_agent(
|
||||
self, runner: CliRunner, project_dir: Path
|
||||
):
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"schedule",
|
||||
"init",
|
||||
"--target",
|
||||
str(project_dir),
|
||||
"--engagement",
|
||||
"demo",
|
||||
"--agents",
|
||||
"not-a-real-agent",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
assert "unknown agent" in result.output
|
||||
|
||||
def test_engagement_flags_require_engagement_slug(
|
||||
self, runner: CliRunner, project_dir: Path
|
||||
):
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
"schedule",
|
||||
"init",
|
||||
"--target",
|
||||
str(project_dir),
|
||||
"--agents",
|
||||
"coach",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 1
|
||||
assert "--engagement" in result.output
|
||||
|
||||
def test_init_no_overwrite_without_force(
|
||||
self, runner: CliRunner, project_dir: Path
|
||||
):
|
||||
|
||||
15
uv.lock
generated
15
uv.lock
generated
@@ -805,7 +805,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "kaizen-agentic"
|
||||
version = "1.3.0"
|
||||
version = "1.4.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "click", version = "8.1.8", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" },
|
||||
@@ -835,6 +835,9 @@ dev = [
|
||||
{ name = "pytest-cov", version = "5.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "pytest-cov", version = "7.1.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.9'" },
|
||||
]
|
||||
events = [
|
||||
{ name = "nats-py" },
|
||||
]
|
||||
test = [
|
||||
{ name = "pytest", version = "8.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.9'" },
|
||||
{ name = "pytest", version = "8.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.9.*'" },
|
||||
@@ -852,6 +855,7 @@ requires-dist = [
|
||||
{ name = "click", specifier = ">=8.0.0" },
|
||||
{ name = "flake8", marker = "extra == 'dev'", specifier = ">=5.0.0" },
|
||||
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.0.0" },
|
||||
{ name = "nats-py", marker = "extra == 'events'", specifier = ">=2.6.0" },
|
||||
{ name = "pre-commit", marker = "extra == 'dev'", specifier = ">=2.20.0" },
|
||||
{ name = "pydantic", specifier = ">=2.0.0" },
|
||||
{ name = "pytest", marker = "extra == 'dev'", specifier = ">=6.0.0" },
|
||||
@@ -1149,6 +1153,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nats-py"
|
||||
version = "2.15.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/02/f0/fc5e93f2b0dd14a202590ad9d30eda1955ea872039b5204357348d0f4b1e/nats_py-2.15.0.tar.gz", hash = "sha256:6622c547d9a7d2313d9c147d46c386188f4ec2c7b5c9f9a0438a4d1b55f54a93", size = 75995 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/db/a8/b55606c7c621fb813c8ec78baf201d2c78bf6051091ec0c7ada572999e95/nats_py-2.15.0-py3-none-any.whl", hash = "sha256:9f8d36aa52a9926a88b8f1d70cf1fdce0ad387941479b500ee9ab3e51073cefd", size = 90334 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nodeenv"
|
||||
version = "1.10.0"
|
||||
|
||||
@@ -111,6 +111,17 @@ runs `kaizen-agentic schedule prepare <agent>`.
|
||||
|
||||
**Workplan:** KAIZEN-WP-0004 Part 2 + KAIZEN-WP-0006. Patterns: [docs/INTEGRATION_PATTERNS.md](../docs/INTEGRATION_PATTERNS.md).
|
||||
|
||||
**Customer engagement (WP-0008, ADR-006)** — a customer repo (e.g. coulomb-loop)
|
||||
orchestrates loops across a fleet roster; supplier ships playbook + CLI:
|
||||
|
||||
- [customer-engagement-playbook](../docs/integrations/customer-engagement-playbook.md)
|
||||
- [customer-engagement-repo-layout](../docs/integrations/customer-engagement-repo-layout.md)
|
||||
- [ADR-006](../docs/adr/ADR-006-customer-engagement-convention.md)
|
||||
- CLI: `schedule init --engagement`, `metrics record --emit-event`
|
||||
- activity-core handoff: [activity-core-handoff-engagement](../docs/integrations/activity-core-handoff-engagement.md)
|
||||
|
||||
Reference customer: coulomb-loop `INTENT.md` (coulomb_social domain).
|
||||
|
||||
### artifact-store (P1)
|
||||
|
||||
**Evidence retention** — durable registry for generated outputs.
|
||||
|
||||
@@ -0,0 +1,303 @@
|
||||
---
|
||||
id: KAIZEN-WP-0008
|
||||
type: workplan
|
||||
title: "Coulomb-loop supplier engagement (customer-repo playbook)"
|
||||
domain: custodian
|
||||
repo: kaizen-agentic
|
||||
status: done
|
||||
owner: kaizen-agentic
|
||||
topic_slug: custodian
|
||||
customer_repo: coulomb-loop
|
||||
created: "2026-06-18"
|
||||
updated: "2026-06-18"
|
||||
depends_on:
|
||||
- KAIZEN-WP-0006
|
||||
- KAIZEN-WP-0004
|
||||
tasks:
|
||||
- id: T01
|
||||
status: done
|
||||
title: Document customer engagement repo layout from coulomb-loop reference
|
||||
- id: T02
|
||||
status: done
|
||||
title: Add docs/integrations/customer-engagement-playbook.md skeleton
|
||||
- id: T03
|
||||
status: done
|
||||
title: Implement metrics record --emit-event for kaizen.metrics.recorded
|
||||
- id: T04
|
||||
status: done
|
||||
title: Add schedule init --engagement mode for customer repos
|
||||
- id: T05
|
||||
status: done
|
||||
title: Support pilot schedule init on kaizen-agentic the-custodian activity-core
|
||||
- id: T06
|
||||
status: done
|
||||
title: Draft ADR-006 customer engagement convention
|
||||
- id: T07
|
||||
status: done
|
||||
title: Absorb coulomb-loop supplier-notes into playbook v1
|
||||
- id: T08
|
||||
status: done
|
||||
title: ActivityDefinition override manifest design for hybrid sync
|
||||
- id: T09
|
||||
status: done
|
||||
title: Tests for emit-event and engagement init
|
||||
- id: T10
|
||||
status: done
|
||||
title: Update CHANGELOG wiki and cross-link coulomb-loop INTENT
|
||||
state_hub_workstream_id: "80f473eb-d052-4f50-a633-806f03c469be"
|
||||
---
|
||||
|
||||
# KAIZEN-WP-0008 — Coulomb-loop Supplier Engagement
|
||||
|
||||
**Status:** done
|
||||
**Owner:** kaizen-agentic (supplier)
|
||||
**Customer:** `coulomb-loop` (coulomb_social domain)
|
||||
**Depends on:** WP-0006 (schedule contract), WP-0004 (activity-core integration)
|
||||
|
||||
## Goal
|
||||
|
||||
Deliver supplier capabilities for Coulomb's self-improvement loop engagement and
|
||||
**generalize learnings** into a reusable customer-repo bootstrap playbook — so the
|
||||
next engagement requires ≤50% setup effort compared to coulomb-loop.
|
||||
|
||||
This workplan is the **supplier mirror** of coulomb-loop LOOP-WP-0001–0004.
|
||||
Customer-specific operations stay in `coulomb-loop`; reusable IP stays here.
|
||||
|
||||
## Engagement model
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
CL[coulomb-loop customer]
|
||||
KA[kaizen-agentic supplier]
|
||||
TR[target repos fleet]
|
||||
AC[activity-core]
|
||||
|
||||
CL -->|contracts rosters definitions| AC
|
||||
KA -->|agents CLI ADRs playbook| CL
|
||||
KA -->|schedule prepare metrics| TR
|
||||
AC -->|tasks| TR
|
||||
```
|
||||
|
||||
See coulomb-loop `docs/adr/ADR-002-customer-supplier-boundary.md`.
|
||||
|
||||
## Sequencing (per DEC-004 default — smoke-first)
|
||||
|
||||
```
|
||||
Part 1 (T01–T02, T05) ── parallel with coulomb-loop smoke test
|
||||
Part 2 (T03–T04, T06–T09) ── after first hourly E2E pass
|
||||
Part 3 (T07–T08, T10) ── after LOOP-WP-0004 supplier-notes available
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Part 1 — Document and support smoke test
|
||||
|
||||
## Document customer engagement repo layout
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T01
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "177bb16c-6239-43f2-8d99-f4498c31d74a"
|
||||
```
|
||||
|
||||
Create `docs/integrations/customer-engagement-repo-layout.md` from coulomb-loop
|
||||
reference:
|
||||
|
||||
```
|
||||
customer-repo/
|
||||
INTENT.md SCOPE.md
|
||||
workplans/LOOP-WP-* or <PREFIX>-WP-*
|
||||
docs/adr/ docs/decisions/
|
||||
history/
|
||||
activity-definitions/ # customer-owned copies
|
||||
loops/<loop-id>/ # roster cadence health
|
||||
```
|
||||
|
||||
No code — layout contract only.
|
||||
|
||||
## Playbook skeleton
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T02
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "90bd0fc2-6e49-4a59-9a78-91e749cef8a6"
|
||||
```
|
||||
|
||||
Add `docs/integrations/customer-engagement-playbook.md`:
|
||||
|
||||
1. Register repo (state-hub `register_project.sh`)
|
||||
2. Write INTENT + 4 loop workplans
|
||||
3. Run `fix-consistency`
|
||||
4. Pilot `schedule init` on target repos
|
||||
5. Sync ActivityDefinitions to activity-core
|
||||
6. Bootstrap hourly → regulator promotes cadence
|
||||
|
||||
Link to coulomb-loop as reference implementation.
|
||||
|
||||
## Support pilot schedule init
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T05
|
||||
status: done
|
||||
priority: high
|
||||
state_hub_task_id: "a48598b7-2a33-46ef-8594-6a2702459f39"
|
||||
```
|
||||
|
||||
Completed 2026-06-18 on kaizen-agentic, the-custodian, activity-core. Bootstrap
|
||||
hourly crons patched manually; friction logged in coulomb-loop `supplier-notes.md`.
|
||||
|
||||
Execute on pilot repos (after DEC-001 approval):
|
||||
|
||||
```bash
|
||||
for repo in kaizen-agentic the-custodian activity-core; do
|
||||
cd ~/$repo
|
||||
kaizen-agentic schedule init --timezone Europe/Berlin
|
||||
kaizen-agentic memory init coach
|
||||
kaizen-agentic memory init optimization
|
||||
kaizen-agentic schedule validate
|
||||
done
|
||||
```
|
||||
|
||||
Record friction in coulomb-loop `loops/kaizen-stack/supplier-notes.md`.
|
||||
|
||||
---
|
||||
|
||||
## Part 2 — Supplier automation
|
||||
|
||||
## metrics record --emit-event
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T03
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "26ee0f8d-2b69-4796-b276-b76238d67546"
|
||||
```
|
||||
|
||||
Emit NATS event `kaizen.metrics.recorded` when flag set:
|
||||
|
||||
```bash
|
||||
kaizen-agentic metrics record coach --success --time 120 --quality 0.9 --emit-event
|
||||
```
|
||||
|
||||
Payload per coulomb-loop LOOP-WP-0002 T03 / `low-success-rate-review` definition.
|
||||
Default: off (backward compatible).
|
||||
|
||||
## schedule init --engagement
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T04
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "62324bd2-1737-4864-889c-56179d0d11e8"
|
||||
```
|
||||
|
||||
Scaffold customer-target schedule with bootstrap crons:
|
||||
|
||||
```bash
|
||||
kaizen-agentic schedule init --engagement coulomb-loop \
|
||||
--agents coach,optimization --bootstrap-cadence hourly
|
||||
```
|
||||
|
||||
Writes hourly crons per ADR-003; documents engagement slug in schedule comment.
|
||||
|
||||
## ADR-006 customer engagement convention
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T06
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "5c06cdd9-655d-4837-b725-1f89b83db6d4"
|
||||
```
|
||||
|
||||
`docs/adr/ADR-006-customer-engagement-convention.md` — formalize supplier/customer
|
||||
split, `.kaizen/` placement in target repos, playbook lifecycle.
|
||||
|
||||
## Tests
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T09
|
||||
status: done
|
||||
priority: medium
|
||||
state_hub_task_id: "f45077ea-5d24-4a85-bac2-ab9a3f61c20b"
|
||||
```
|
||||
|
||||
Covered by `tests/test_metrics_emit_event.py` and `tests/test_schedule_cli.py`
|
||||
(engagement init + module presets).
|
||||
|
||||
---
|
||||
|
||||
## Part 3 — Playbook v1 and hybrid sync design
|
||||
|
||||
## Absorb supplier-notes into playbook v1
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T07
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "0ef49fb5-af2f-4adf-aa90-1ea2cf389d00"
|
||||
```
|
||||
|
||||
After LOOP-WP-0004 T07 draft in coulomb-loop, merge into playbook v1.
|
||||
Target: second customer can copy template repo and run checklist in one session.
|
||||
|
||||
## ActivityDefinition override manifest
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T08
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "c9bee570-89b5-43e5-aabc-23c7dcc4e30c"
|
||||
```
|
||||
|
||||
Design-only (implements DEC-003 option C): YAML manifest mapping supplier
|
||||
definition id → customer cron/labels/enabled overrides. No runtime in v1.
|
||||
|
||||
## Documentation release
|
||||
|
||||
```task
|
||||
id: KAIZEN-WP-0008-T10
|
||||
status: done
|
||||
priority: low
|
||||
state_hub_task_id: "052a592b-ae7c-4213-9e09-eb8b37119d5e"
|
||||
```
|
||||
|
||||
Update `wiki/EcosystemIntegration.md`, `CHANGELOG [Unreleased]`, cross-link
|
||||
coulomb-loop INTENT from `docs/integrations/customer-engagement-playbook.md`.
|
||||
|
||||
---
|
||||
|
||||
## ADR-004 follow-on (customer accepted 2026-06-18)
|
||||
|
||||
After bootstrap metrics baseline, supplier may add:
|
||||
|
||||
```
|
||||
kaizen-agentic metrics rotation-signals [--target PATH]
|
||||
```
|
||||
|
||||
Reads `.kaizen/metrics/` + optimizer output; emits saturation score per
|
||||
`coulomb-loop/loops/regulator/rotation-policy.yml`. Feeds LOOP-WP-0004 T09.
|
||||
Track as KAIZEN-WP-0008 extension task if needed after T03 ships.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- activity-core resolver implementation (activity-core repo)
|
||||
- coulomb-loop workplan execution (customer repo)
|
||||
- Fleet-wide rollout beyond agreed pilot (DEC-001)
|
||||
|
||||
## Success criteria
|
||||
|
||||
1. Pilot repos have valid `.kaizen/schedule.yml` via supplier CLI
|
||||
2. `metrics record --emit-event` enables LOOP-WP-0002 event path
|
||||
3. Playbook v1 committed; coulomb-loop cited as reference
|
||||
4. ADR-006 accepted
|
||||
|
||||
## Customer workplans (do not duplicate here)
|
||||
|
||||
| Customer WP | Supplier support |
|
||||
|-------------|------------------|
|
||||
| LOOP-WP-0001 | T05 schedule init; activity-core handoff docs |
|
||||
| LOOP-WP-0002 | T03 emit-event |
|
||||
| LOOP-WP-0003 | scope-analyst agent (existing) |
|
||||
| LOOP-WP-0004 | T07 playbook feedback |
|
||||
Reference in New Issue
Block a user