Compare commits
7 Commits
v1.3.0
...
93bf49479b
| Author | SHA1 | Date | |
|---|---|---|---|
| 93bf49479b | |||
| 1641a3165d | |||
| c5798f58e4 | |||
| 7424893758 | |||
| d220bae007 | |||
| 843cf4eee0 | |||
| 7058859e5c |
@@ -17,7 +17,7 @@ Packaged copies live in `src/kaizen_agentic/data/agents/` for `pip install` dist
|
|||||||
|----------|--------|
|
|----------|--------|
|
||||||
| Testing | `tdd-workflow`, `test-maintenance`, `testing-efficiency` |
|
| Testing | `tdd-workflow`, `test-maintenance`, `testing-efficiency` |
|
||||||
| Quality | `code-refactoring`, `datamodel-optimization` |
|
| Quality | `code-refactoring`, `datamodel-optimization` |
|
||||||
| Process | `requirements-engineering`, `keepaTodofile`, `keepaChangelog`, `keepaContributingfile`, `project-management`, `priority-evaluation`, `scope-analyst` |
|
| Process | `requirements-engineering`, `keepaTodofile`, `keepaChangelog`, `keepaContributingfile`, `project-assistant`, `priority-evaluation`, `scope-analyst` |
|
||||||
| Infrastructure | `setupRepository`, `tooling-optimization`, `sys-medic` |
|
| Infrastructure | `setupRepository`, `tooling-optimization`, `sys-medic` |
|
||||||
| Release | `releaseManager` |
|
| Release | `releaseManager` |
|
||||||
| Docs | `claude-documentation` |
|
| Docs | `claude-documentation` |
|
||||||
|
|||||||
@@ -2,12 +2,23 @@
|
|||||||
# Custodian Brief — kaizen-agentic
|
# Custodian Brief — kaizen-agentic
|
||||||
|
|
||||||
**Domain:** custodian
|
**Domain:** custodian
|
||||||
**Last synced:** 2026-06-16 23:04 UTC
|
**Last synced:** 2026-06-18 02:54 UTC
|
||||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||||
|
|
||||||
## Active Workstreams
|
## Active Workstreams
|
||||||
|
|
||||||
*(none — repo may need first-session setup)*
|
### Coulomb-loop supplier engagement (customer-repo playbook)
|
||||||
|
Progress: 1/10 done | workstream_id: `80f473eb-d052-4f50-a633-806f03c469be`
|
||||||
|
|
||||||
|
**Open tasks:**
|
||||||
|
- · Document customer engagement repo layout `177bb16c`
|
||||||
|
- · Playbook skeleton `90bd0fc2`
|
||||||
|
- · metrics record --emit-event `26ee0f8d`
|
||||||
|
- · schedule init --engagement `62324bd2`
|
||||||
|
- · ADR-006 customer engagement convention `5c06cdd9`
|
||||||
|
- · Tests `f45077ea`
|
||||||
|
- · Absorb supplier-notes into playbook v1 `0ef49fb5`
|
||||||
|
- … and 2 more open tasks
|
||||||
|
|
||||||
---
|
---
|
||||||
## MCP Orientation (when available)
|
## MCP Orientation (when available)
|
||||||
|
|||||||
17
.kaizen/schedule.yml
Normal file
17
.kaizen/schedule.yml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Kaizen scheduled agent execution (ADR-005)
|
||||||
|
# Engagement: coulomb-loop bootstrap — hourly crons, daily cadence enum
|
||||||
|
# Regulator promotes to daily/weekly per ADR-003
|
||||||
|
version: '1'
|
||||||
|
timezone: Europe/Berlin
|
||||||
|
agents:
|
||||||
|
coach:
|
||||||
|
cadence: daily
|
||||||
|
cron: "15 * * * *"
|
||||||
|
enabled: true
|
||||||
|
optimization:
|
||||||
|
cadence: daily
|
||||||
|
cron: "30 * * * *"
|
||||||
|
enabled: true
|
||||||
|
tdd-workflow:
|
||||||
|
cadence: monthly
|
||||||
|
enabled: false
|
||||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -7,6 +7,39 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
### 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`)
|
||||||
|
- **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
|
||||||
|
- **Agent authoring & doc generation (WP-0007)** — `kaizen-agentic create-agent`
|
||||||
|
scaffolds a schema-valid agent; `kaizen-agentic docs generate [--check]`
|
||||||
|
refreshes the CLAUDE.md `## Installed Agents` section idempotently
|
||||||
|
- **Frontmatter schema validation** — `kaizen-agentic validate` now enforces
|
||||||
|
required `name`/`description`/`category`, a known category, and valid
|
||||||
|
`memory`/`model` values with actionable errors
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- **Idempotent doc regeneration** — `_update_documentation` no longer duplicates
|
||||||
|
the `## Installed Agents` block on each run (regex stopped at the first `###`
|
||||||
|
subheading); rendering is now a shared, idempotent helper
|
||||||
|
- **Declared category honoured** — agent frontmatter `category` is authoritative
|
||||||
|
when valid (name/content heuristic is fallback only)
|
||||||
|
- **Installed-agent resolution** — `list_installed_agents` reads the frontmatter
|
||||||
|
name, so agents whose filename differs from their name resolve correctly
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **Renamed `agent-project-management.md` → `agent-project-assistant.md`** to
|
||||||
|
satisfy the `agent-<name>.md` convention (frontmatter `name: project-assistant`);
|
||||||
|
eliminates a registry name/filename collision
|
||||||
|
|
||||||
## [1.3.0] - 2026-06-17
|
## [1.3.0] - 2026-06-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
24
CLAUDE.md
24
CLAUDE.md
@@ -12,51 +12,45 @@
|
|||||||
|
|
||||||
This project includes the following specialized agents:
|
This project includes the following specialized agents:
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
- **tdd-workflow**: Expert guidance for the TDD8 workflow methodology, specializing in the comprehensive ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH cycle with sophisticated sidequest management and proper test organization.
|
|
||||||
|
|
||||||
Use these agents by referencing them in your Claude Code interactions.
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
|
|
||||||
- **claude-documentation**: Specialized assistant for Claude and Claude Code documentation, features, and best practices
|
- **claude-documentation**: Specialized assistant for Claude and Claude Code documentation, features, and best practices
|
||||||
|
- **keepaContributingfile**: Specialized assistant for maintaining CONTRIBUTING.md files following Keep a Contributing-File V0.0.1 format within the Kaizen Agentic framework
|
||||||
|
- **wisdom-encouragement**: Provides encouraging wisdom and guidance for complex implementation tasks and challenging technical work
|
||||||
|
|
||||||
### Meta
|
### Meta
|
||||||
|
|
||||||
- **coach**: Coaching meta-agent that reads all agent memories in a project and synthesises cross-agent briefs and new-agent orientations
|
- **coach**: Coaching meta-agent that reads all agent memories in a project and synthesises cross-agent briefs and new-agent orientations
|
||||||
|
- **optimization**: Meta-agent that analyzes and optimizes other Claude Code subagents based on their performance data, usage patterns, and effectiveness metrics. Use PROACTIVELY for agent ecosystem improvement.
|
||||||
|
|
||||||
### Code Quality
|
### Code Quality
|
||||||
|
|
||||||
- **code-refactoring**: Analyze code structure and quality, identify improvement opportunities, and provide actionable refactoring guidance. Use PROACTIVELY for code quality assessment and improvement.
|
- **code-refactoring**: Analyze code structure and quality, identify improvement opportunities, and provide actionable refactoring guidance. Use PROACTIVELY for code quality assessment and improvement.
|
||||||
- **datamodel-optimization**: Specialized agent that systematically analyzes, optimizes, and enhances dataclasses, models, and data structures within a codebase. Provides comprehensive datamodel improvements including convenience methods, interface consistency, code reduction, and test alignment.
|
- **datamodel-optimization**: Specialized agent that systematically analyzes, optimizes, and enhances dataclasses, models, and data structures within a codebase. Provides comprehensive datamodel improvements including convenience methods, interface consistency, code reduction, and test alignment.
|
||||||
- **optimization**: Meta-agent that analyzes and optimizes other Claude Code subagents based on their performance data, usage patterns, and effectiveness metrics. Use PROACTIVELY for agent ecosystem improvement.
|
|
||||||
- **tooling-optimization**: Meta-agent that analyzes and optimizes repository tooling usage to improve development efficiency
|
|
||||||
|
|
||||||
### Project Management
|
### Project Management
|
||||||
|
|
||||||
- **keepaChangelog**: Specialized assistant for maintaining CHANGELOG.md files following Keep a Changelog format
|
- **keepaChangelog**: Specialized assistant for maintaining CHANGELOG.md files following Keep a Changelog format
|
||||||
- **keepaContributingfile**: Specialized assistant for maintaining CONTRIBUTING.md files following Keep a Contributing-File V0.0.1 format within the Kaizen Agentic framework
|
|
||||||
- **keepaTodofile**: Specialized assistant for maintaining TODO.md files following Keep a Todofile V0.0.1 format
|
- **keepaTodofile**: Specialized assistant for maintaining TODO.md files following Keep a Todofile V0.0.1 format
|
||||||
|
- **priority-evaluation**: Specialized assistant to help evaluate and establish priorities for issues and tasks.
|
||||||
|
- **project-assistant**: Specialized assistant for project status, progress tracking, and development planning
|
||||||
|
- **releaseManager**: Manages software releases, version control, and publication workflows for Python packages
|
||||||
|
- **scope-analyst**: Analyze a repository and produce/improve SCOPE.md for rapid orientation
|
||||||
|
|
||||||
### Development Process
|
### Development Process
|
||||||
|
|
||||||
- **priority-evaluation**: Specialized assistant to help evaluate and establish priorities for issues and tasks.
|
|
||||||
- **releaseManager**: Manages software releases, version control, and publication workflows for Python packages
|
|
||||||
- **requirements-engineering**: Specialized agent designed to prevent interface compatibility issues and mock object mismatches by ensuring solid foundation planning before implementation. Based on lessons learned from Issue
|
- **requirements-engineering**: Specialized agent designed to prevent interface compatibility issues and mock object mismatches by ensuring solid foundation planning before implementation. Based on lessons learned from Issue
|
||||||
- **scope-analyst**: Analyze a repository and produce/improve SCOPE.md for rapid orientation
|
- **tdd-workflow**: Expert guidance for the TDD8 workflow methodology, specializing in the comprehensive ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH cycle with sophisticated sidequest management and proper test organization.
|
||||||
- **wisdom-encouragement**: Provides encouraging wisdom and guidance for complex implementation tasks and challenging technical work
|
|
||||||
|
|
||||||
### Infrastructure
|
### Infrastructure
|
||||||
|
|
||||||
- **setupRepository**: Specialized assistant for setting up new Python repositories following PythonVibes best practices
|
- **setupRepository**: Specialized assistant for setting up new Python repositories following PythonVibes best practices
|
||||||
- **sys-medic**: Linux/Kubernetes node health assessment agent — diagnoses process, memory, CPU, disk, network, and kubelet issues with safe, prioritized, evidence-driven guidance
|
- **sys-medic**: Linux/Kubernetes node health assessment agent — diagnoses process, memory, CPU, disk, network, and kubelet issues with safe, prioritized, evidence-driven guidance
|
||||||
|
- **tooling-optimization**: Meta-agent that analyzes and optimizes repository tooling usage to improve development efficiency
|
||||||
|
|
||||||
### Testing
|
### Testing
|
||||||
|
|
||||||
- **tdd-workflow**: Expert guidance for the TDD8 workflow methodology, specializing in the comprehensive ISSUE-TEST-RED-GREEN-REFACTOR-DOCUMENT-REFINE-PUBLISH cycle with sophisticated sidequest management and proper test organization.
|
|
||||||
- **test-maintenance**: Specialized agent for analyzing and fixing failing tests in the project
|
- **test-maintenance**: Specialized agent for analyzing and fixing failing tests in the project
|
||||||
- **testing-efficiency**: Specialized agent designed to optimize TDD8 workflow test execution, resolve pytest reliability issues, and enhance overall testing efficiency for red-green iterations. Focuses on smart test selection, parallel execution, and agent integration patterns.
|
- **testing-efficiency**: Specialized agent designed to optimize TDD8 workflow test execution, resolve pytest reliability issues, and enhance overall testing efficiency for red-green iterations. Focuses on smart test selection, parallel execution, and agent integration patterns.
|
||||||
|
|
||||||
Use these agents by referencing them in your Claude Code interactions.
|
Use these agents by referencing them in your Claude Code interactions.
|
||||||
|
|
||||||
|
|||||||
28
TODO.md
28
TODO.md
@@ -10,23 +10,27 @@ The structure organizes **future tasks** by their impact, just as a changelog or
|
|||||||
|
|
||||||
## [Unreleased] - *Active Vibe-Coding State* 💡
|
## [Unreleased] - *Active Vibe-Coding State* 💡
|
||||||
|
|
||||||
Tasks in workplan: `workplans/kaizen-agentic-WP-0006-scheduled-agent-execution.md` (v1.3.0)
|
Tasks in workplan: `workplans/kaizen-agentic-WP-0007-agent-authoring-doc-generation.md` (v1.4.0)
|
||||||
|
|
||||||
### Implemented (pending v1.3.0 tag)
|
### 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
|
* **ADR-005 + `.kaizen/schedule.yml`** — scheduled agent execution contract
|
||||||
* **`kaizen-agentic schedule`** — validate, init, prepare, list
|
* **`kaizen-agentic schedule`** — validate, init, prepare, list
|
||||||
* **activity-core definitions** — weekly coach + optimization on preselected repos
|
* **activity-core definitions** — weekly coach + optimization on preselected repos
|
||||||
* **Resolver + roster + event design** — `discover_kaizen_scheduled_repos`,
|
|
||||||
State Hub roster fields, `kaizen.schedule.prepared` payload, handoff checklist
|
|
||||||
|
|
||||||
### To Add (release)
|
### Deferred / future
|
||||||
* **Tag v1.3.0** — once activity-core handoff issue is opened and pilot smoke-tested
|
* Interactive agent selection wizard (multi-step) — `create-agent` covers the
|
||||||
* **activity-core implementation** — resolver + sync (separate repo; see handoff doc)
|
single-agent scaffold; a guided multi-agent wizard remains future work
|
||||||
|
* Multi-file agent packages / protocol scaffolding
|
||||||
### Deferred to WP-0007 (v1.3.0+)
|
|
||||||
* Interactive agent selection wizard
|
|
||||||
* Agent template schema validation in `validate`
|
|
||||||
* Documentation generation from agent metadata
|
|
||||||
|
|
||||||
***
|
***
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,28 @@ kaizen-agentic remove old-agent-name
|
|||||||
|
|
||||||
# Project status
|
# Project status
|
||||||
kaizen-agentic status # Show current project status
|
kaizen-agentic status # Show current project status
|
||||||
kaizen-agentic validate # Validate agent installation
|
kaizen-agentic validate # Validate agents (incl. frontmatter schema)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Authoring & Docs (WP-0007)
|
||||||
|
```bash
|
||||||
|
# Scaffold a new schema-valid agent (agents/agent-<name>.md)
|
||||||
|
kaizen-agentic create-agent my-agent -c testing -d "What it does"
|
||||||
|
kaizen-agentic create-agent my-agent # prompts for category + description
|
||||||
|
kaizen-agentic create-agent my-agent --force # overwrite existing
|
||||||
|
|
||||||
|
# Refresh the CLAUDE.md "Installed Agents" section from agent metadata
|
||||||
|
kaizen-agentic docs generate # idempotent rewrite
|
||||||
|
kaizen-agentic docs generate --check # CI gate: non-zero if out of date
|
||||||
|
|
||||||
|
# validate enforces required name/description/category, valid category,
|
||||||
|
# and valid memory/model values
|
||||||
|
kaizen-agentic validate
|
||||||
|
```
|
||||||
|
|
||||||
|
After adding or editing agents, run `make agents-sync-package` so the packaged
|
||||||
|
`data/agents/` copies stay in parity (release-check gate).
|
||||||
|
|
||||||
### Project Metrics (ADR-004)
|
### Project Metrics (ADR-004)
|
||||||
```bash
|
```bash
|
||||||
# Record outcome at session close
|
# Record outcome at session close
|
||||||
@@ -93,6 +112,8 @@ Session-close template: `docs/templates/session-close-protocol.md`
|
|||||||
# Opt this repo into fleet scheduling
|
# Opt this repo into fleet scheduling
|
||||||
kaizen-agentic schedule init # coach + optimization weekly
|
kaizen-agentic schedule init # coach + optimization weekly
|
||||||
kaizen-agentic schedule init --timezone UTC # override timezone
|
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 validate # schema + agent-name checks
|
||||||
kaizen-agentic schedule list # enabled entries (--all incl. disabled)
|
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` |
|
| [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 |
|
| [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):**
|
**Activation handoff (activity-core owners):**
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ Memory-enabled agents record per-session outcomes at close:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1>
|
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 metrics optimize [agent]
|
||||||
kaizen-agentic memory brief <agent> # includes Performance Summary
|
kaizen-agentic memory brief <agent> # includes Performance Summary
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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 [--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 validate [--target PATH]
|
||||||
kaizen-agentic schedule list [--target PATH] [--all]
|
kaizen-agentic schedule list [--target PATH] [--all]
|
||||||
kaizen-agentic schedule prepare <agent> [--target PATH] [--format markdown|json]
|
kaizen-agentic schedule prepare <agent> [--target PATH] [--format markdown|json]
|
||||||
|
|||||||
@@ -80,6 +80,23 @@ memory: enabled # or: disabled
|
|||||||
|
|
||||||
The `memory` field defaults to `enabled`. Set `memory: disabled` for agents that are stateless by design (e.g. `wisdom-encouragement`).
|
The `memory` field defaults to `enabled`. Set `memory: disabled` for agents that are stateless by design (e.g. `wisdom-encouragement`).
|
||||||
|
|
||||||
|
The file name must follow `agent-<name>.md` where `<name>` equals the frontmatter
|
||||||
|
`name`. `kaizen-agentic validate` enforces the frontmatter schema (required
|
||||||
|
fields, known `category`, valid `memory`/`model`).
|
||||||
|
|
||||||
|
### Authoring and doc sync (WP-0007)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
kaizen-agentic create-agent <name> -c <category> -d "<description>"
|
||||||
|
kaizen-agentic validate # schema + dependency checks
|
||||||
|
kaizen-agentic docs generate # refresh CLAUDE.md Installed Agents
|
||||||
|
make agents-sync-package # keep packaged data/agents/ in parity
|
||||||
|
```
|
||||||
|
|
||||||
|
`create-agent` writes a schema-valid skeleton; `docs generate` rewrites the
|
||||||
|
project `## Installed Agents` section **idempotently** (use `--check` as a CI
|
||||||
|
gate). The section is grouped by the declared frontmatter `category`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## The Coach Meta-Agent
|
## The Coach Meta-Agent
|
||||||
|
|||||||
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)
|
||||||
@@ -35,7 +35,9 @@ action:
|
|||||||
|
|
||||||
**Threshold:** 0.8 success rate, minimum 5 executions (avoids noise on early pilots).
|
**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
|
```bash
|
||||||
kaizen-agentic metrics show <agent> # inspect summary.success_rate
|
kaizen-agentic metrics show <agent> # inspect summary.success_rate
|
||||||
|
|||||||
@@ -33,15 +33,15 @@ returns one `scheduled_run` per `(repo, agent)`; this definition selects the
|
|||||||
id: run-weekly-coach
|
id: run-weekly-coach
|
||||||
for_each: context.scheduled_runs
|
for_each: context.scheduled_runs
|
||||||
bind_as: r
|
bind_as: r
|
||||||
condition: 'r.agent == "coach" and r.enabled == true'
|
condition: 'context.r.agent == "coach" and context.r.enabled'
|
||||||
action:
|
action:
|
||||||
task_template: "Weekly coach orientation: {{r.repo}}"
|
task_template: "Weekly coach orientation: {context.r.repo}"
|
||||||
description: |
|
description: |
|
||||||
{{r.prepare_command}}
|
{context.r.prepare_command}
|
||||||
Then load agents/agent-coach.md in a coding-agent session, paste the
|
Then load agents/agent-coach.md in a coding-agent session, paste the
|
||||||
prepared bundle, and follow the coach synthesis. At session close:
|
prepared bundle, and follow the coach synthesis. At session close:
|
||||||
kaizen-agentic metrics record coach --success --time <s> --quality <0-1>
|
kaizen-agentic metrics record coach --success --time <s> --quality <0-1>
|
||||||
target_repo: "{{r.repo}}"
|
target_repo: context.r.repo
|
||||||
priority: medium
|
priority: medium
|
||||||
labels: ["kaizen", "agent-run", "coach", "scheduled", "automated"]
|
labels: ["kaizen", "agent-run", "coach", "scheduled", "automated"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ Invokes the kaizen-agentic optimizer CLI per project.
|
|||||||
id: run-weekly-optimizer
|
id: run-weekly-optimizer
|
||||||
for_each: context.projects
|
for_each: context.projects
|
||||||
bind_as: p
|
bind_as: p
|
||||||
condition: 'p.has_metrics == true'
|
condition: 'context.p.has_metrics'
|
||||||
action:
|
action:
|
||||||
task_template: "Run kaizen metrics optimize on {{p.repo}}"
|
task_template: "Run kaizen metrics optimize on {context.p.repo}"
|
||||||
description: |
|
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)
|
Optional: kaizen-agentic metrics publish (when artifact-store configured)
|
||||||
target_repo: "{{p.repo}}"
|
target_repo: context.p.repo
|
||||||
priority: medium
|
priority: medium
|
||||||
labels: ["kaizen", "metrics", "optimizer", "automated"]
|
labels: ["kaizen", "metrics", "optimizer", "automated"]
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -30,16 +30,16 @@ review is evidence-backed.
|
|||||||
id: run-weekly-optimization
|
id: run-weekly-optimization
|
||||||
for_each: context.scheduled_runs
|
for_each: context.scheduled_runs
|
||||||
bind_as: r
|
bind_as: r
|
||||||
condition: 'r.agent == "optimization" and r.enabled == true'
|
condition: 'context.r.agent == "optimization" and context.r.enabled'
|
||||||
action:
|
action:
|
||||||
task_template: "Weekly optimization review: {{r.repo}}"
|
task_template: "Weekly optimization review: {context.r.repo}"
|
||||||
description: |
|
description: |
|
||||||
{{r.prepare_command}}
|
{context.r.prepare_command}
|
||||||
kaizen-agentic metrics optimize # refresh evidence
|
kaizen-agentic metrics optimize # refresh evidence
|
||||||
Then load agents/agent-optimization.md, paste the prepared bundle plus the
|
Then load agents/agent-optimization.md, paste the prepared bundle plus the
|
||||||
optimizer report, and act on recommendations. At session close:
|
optimizer report, and act on recommendations. At session close:
|
||||||
kaizen-agentic metrics record optimization --success --time <s> --quality <0-1>
|
kaizen-agentic metrics record optimization --success --time <s> --quality <0-1>
|
||||||
target_repo: "{{r.repo}}"
|
target_repo: context.r.repo
|
||||||
priority: medium
|
priority: medium
|
||||||
labels: ["kaizen", "agent-run", "optimization", "scheduled", "automated"]
|
labels: ["kaizen", "agent-run", "optimization", "scheduled", "automated"]
|
||||||
```
|
```
|
||||||
|
|||||||
66
docs/integrations/customer-engagement-playbook.md
Normal file
66
docs/integrations/customer-engagement-playbook.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Customer Engagement Playbook (supplier)
|
||||||
|
|
||||||
|
How kaizen-agentic supports a **customer engagement repo** (e.g. coulomb-loop)
|
||||||
|
that orchestrates improvement loops across a pilot roster.
|
||||||
|
|
||||||
|
## Roles
|
||||||
|
|
||||||
|
| Repo | Role |
|
||||||
|
|------|------|
|
||||||
|
| Customer (coulomb-loop) | Roster, ActivityDefinition copies, cadence policy, loop health |
|
||||||
|
| Supplier (kaizen-agentic) | Agents, CLI, integration contracts |
|
||||||
|
| Target repos | `.kaizen/` state (schedule, memory, metrics) |
|
||||||
|
| activity-core | Cron + event orchestration, task creation |
|
||||||
|
|
||||||
|
## Bootstrap checklist
|
||||||
|
|
||||||
|
### 1. Customer repo
|
||||||
|
|
||||||
|
- Register engagement in state-hub
|
||||||
|
- Commit pilot roster (`loops/kaizen-stack/roster.yaml`)
|
||||||
|
- Copy ActivityDefinitions to `activity-definitions/`
|
||||||
|
- 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 writes `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
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. activity-core
|
||||||
|
|
||||||
|
Hand off [activity-core-handoff-engagement.md](activity-core-handoff-engagement.md)
|
||||||
|
requirements before enabling event-driven quality escalation.
|
||||||
|
|
||||||
|
## Cadence promotion
|
||||||
|
|
||||||
|
Customer regulator (LOOP-WP-0004) approves promotion. Re-init or patch schedules:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stabilize phase
|
||||||
|
kaizen-agentic schedule init --engagement <slug> \
|
||||||
|
--bootstrap-cadence daily --force
|
||||||
|
|
||||||
|
# Operate phase
|
||||||
|
kaizen-agentic schedule init --engagement <slug> \
|
||||||
|
--bootstrap-cadence weekly --force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- [KAIZEN-WP-0008](../../workplans/kaizen-agentic-WP-0008-coulomb-loop-supplier-engagement.md)
|
||||||
|
- coulomb-loop `INTENT.md` and `workplans/LOOP-WP-*`
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Resolver Spec: `discover_kaizen_scheduled_repos`
|
# 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
|
is the contract an activity-core implementer needs to add the context resolver
|
||||||
that feeds the scheduled-agent ActivityDefinitions (ADR-005).
|
that feeds the scheduled-agent ActivityDefinitions (ADR-005).
|
||||||
|
|
||||||
|
|||||||
69
docs/integrations/kaizen-metrics-recorded-event.md
Normal file
69
docs/integrations/kaizen-metrics-recorded-event.md
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
# Event Payload: `kaizen.metrics.recorded`
|
||||||
|
|
||||||
|
**Status:** implemented — `kaizen-agentic metrics record --emit-event`
|
||||||
|
|
||||||
|
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)
|
||||||
@@ -70,11 +70,18 @@ the distribution) may appear in a schedule.
|
|||||||
kaizen-agentic schedule init # defaults: coach + optimization weekly
|
kaizen-agentic schedule init # defaults: coach + optimization weekly
|
||||||
kaizen-agentic schedule init --timezone UTC # override timezone
|
kaizen-agentic schedule init --timezone UTC # override timezone
|
||||||
kaizen-agentic schedule init --force # overwrite existing
|
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
|
The default scaffold enables `coach` and `optimization` weekly and declares
|
||||||
`tdd-workflow` monthly but **disabled** (operator opts in deliberately).
|
`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
|
## Listing
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "kaizen-agentic"
|
name = "kaizen-agentic"
|
||||||
version = "1.3.0"
|
version = "1.4.0"
|
||||||
description = "AI agent development framework embracing continuous improvement (kaizen)"
|
description = "AI agent development framework embracing continuous improvement (kaizen)"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
@@ -32,6 +32,9 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
|
events = [
|
||||||
|
"nats-py>=2.6.0",
|
||||||
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"pytest>=6.0.0",
|
"pytest>=6.0.0",
|
||||||
"pytest-cov>=4.0.0",
|
"pytest-cov>=4.0.0",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ It also includes a comprehensive agent distribution system for sharing
|
|||||||
specialized agents across projects via CLI tools and package management.
|
specialized agents across projects via CLI tools and package management.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__version__ = "1.3.0"
|
__version__ = "1.4.0"
|
||||||
__author__ = "Kaizen Agentic Team"
|
__author__ = "Kaizen Agentic Team"
|
||||||
|
|
||||||
from .core import Agent, AgentConfig
|
from .core import Agent, AgentConfig
|
||||||
|
|||||||
73
src/kaizen_agentic/agent_docs.py
Normal file
73
src/kaizen_agentic/agent_docs.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
"""Render and idempotently upsert the project ``## Installed Agents`` section.
|
||||||
|
|
||||||
|
Single source of truth for the agent documentation block written into a
|
||||||
|
project's ``CLAUDE.md``. Both :class:`~kaizen_agentic.installer.AgentInstaller`
|
||||||
|
and the ``kaizen-agentic docs generate`` command reuse these helpers so the
|
||||||
|
section is produced and replaced the same way everywhere — and, critically,
|
||||||
|
**idempotently**: regenerating N times yields the same output as once.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from typing import Dict, Iterable, List
|
||||||
|
|
||||||
|
from .registry import AgentDefinition
|
||||||
|
|
||||||
|
SECTION_HEADING = "## Installed Agents"
|
||||||
|
SECTION_FOOTER = (
|
||||||
|
"Use these agents by referencing them in your Claude Code interactions."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Match the Installed Agents block up to the next *top-level* heading (``\n## ``,
|
||||||
|
# with a trailing space so ``### Subsection`` headings inside the block do not
|
||||||
|
# terminate the match) or end-of-file. The previous ``(?=##|\Z)`` form stopped
|
||||||
|
# at the first ``### Category`` subheading and so duplicated the block on every
|
||||||
|
# run (WP-0007 T01).
|
||||||
|
_SECTION_RE = re.compile(r"## Installed Agents.*?(?=\n## (?!#)|\Z)", re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def render_installed_agents_section(agents: Iterable[AgentDefinition]) -> str:
|
||||||
|
"""Render the ``## Installed Agents`` markdown block from agent metadata.
|
||||||
|
|
||||||
|
Agents are grouped by category in first-seen order. The returned string is
|
||||||
|
newline-terminated and contains no trailing top-level heading, so it can be
|
||||||
|
spliced in front of any following section.
|
||||||
|
"""
|
||||||
|
lines: List[str] = [
|
||||||
|
SECTION_HEADING,
|
||||||
|
"",
|
||||||
|
"This project includes the following specialized agents:",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
categories: Dict[str, List[AgentDefinition]] = {}
|
||||||
|
for agent in agents:
|
||||||
|
categories.setdefault(agent.category.value, []).append(agent)
|
||||||
|
|
||||||
|
for category, members in categories.items():
|
||||||
|
lines.append(f"### {category.replace('-', ' ').title()}")
|
||||||
|
lines.append("")
|
||||||
|
for agent in members:
|
||||||
|
lines.append(f"- **{agent.name}**: {agent.description}")
|
||||||
|
lines.append("")
|
||||||
|
|
||||||
|
lines.append(SECTION_FOOTER)
|
||||||
|
lines.append("")
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def upsert_installed_agents_section(content: str, section: str) -> str:
|
||||||
|
"""Return ``content`` with its Installed Agents block replaced or appended.
|
||||||
|
|
||||||
|
Idempotent: if ``content`` already contains the block, exactly that block is
|
||||||
|
replaced (never duplicated); otherwise the section is appended. ``section``
|
||||||
|
is normalised to end with a single trailing blank line.
|
||||||
|
"""
|
||||||
|
section = section.rstrip() + "\n"
|
||||||
|
|
||||||
|
if SECTION_HEADING in content:
|
||||||
|
return _SECTION_RE.sub(lambda _m: section, content, count=1)
|
||||||
|
|
||||||
|
separator = "" if content.endswith("\n\n") or not content else "\n"
|
||||||
|
return content + separator + section
|
||||||
@@ -15,16 +15,26 @@ from .integrations.artifact_store import (
|
|||||||
default_api_url,
|
default_api_url,
|
||||||
publish_optimizer_evidence,
|
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 .integrations.helix import HelixCorrelationAdapter, enrich_helix_correlation
|
||||||
from .metrics import MetricsStore, OptimizerStore, performance_summary_markdown
|
from .metrics import MetricsStore, OptimizerStore, performance_summary_markdown
|
||||||
from .optimization import OptimizationLoop, MIN_SAMPLES_FOR_RECOMMENDATIONS
|
from .optimization import OptimizationLoop, MIN_SAMPLES_FOR_RECOMMENDATIONS
|
||||||
from .schedule import (
|
from .schedule import (
|
||||||
ScheduleError,
|
ScheduleError,
|
||||||
default_schedule_yaml,
|
default_schedule_yaml,
|
||||||
|
engagement_schedule_yaml,
|
||||||
load_schedule,
|
load_schedule,
|
||||||
schedule_path,
|
schedule_path,
|
||||||
validate_schedule,
|
validate_schedule,
|
||||||
)
|
)
|
||||||
|
from .agent_docs import (
|
||||||
|
render_installed_agents_section,
|
||||||
|
upsert_installed_agents_section,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def safe_cli_wrapper():
|
def safe_cli_wrapper():
|
||||||
@@ -418,8 +428,21 @@ def validate(target: str):
|
|||||||
|
|
||||||
target_path = Path(target).resolve()
|
target_path = Path(target).resolve()
|
||||||
|
|
||||||
|
# Validate agent frontmatter schema
|
||||||
|
click.echo("Validating agent frontmatter schema...")
|
||||||
|
schema_errors = registry.validate_frontmatter_schema()
|
||||||
|
|
||||||
|
if schema_errors:
|
||||||
|
click.echo("Frontmatter schema errors:")
|
||||||
|
for agent_file, errors in schema_errors.items():
|
||||||
|
click.echo(f" {agent_file}:")
|
||||||
|
for error in errors:
|
||||||
|
click.echo(f" ❌ {error}")
|
||||||
|
else:
|
||||||
|
click.echo(" ✅ Frontmatter schema validation passed")
|
||||||
|
|
||||||
# Validate registry agents
|
# Validate registry agents
|
||||||
click.echo("Validating agent registry...")
|
click.echo("\nValidating agent registry...")
|
||||||
registry_errors = registry.validate_agents()
|
registry_errors = registry.validate_agents()
|
||||||
|
|
||||||
if registry_errors:
|
if registry_errors:
|
||||||
@@ -1052,6 +1075,11 @@ def metrics():
|
|||||||
@click.option(
|
@click.option(
|
||||||
"--json", "json_input", is_flag=True, help="Read full record JSON from stdin"
|
"--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(
|
def metrics_record(
|
||||||
agent_name: str,
|
agent_name: str,
|
||||||
target: str,
|
target: str,
|
||||||
@@ -1062,6 +1090,7 @@ def metrics_record(
|
|||||||
session_id: Optional[str],
|
session_id: Optional[str],
|
||||||
idempotency_key: Optional[str],
|
idempotency_key: Optional[str],
|
||||||
json_input: bool,
|
json_input: bool,
|
||||||
|
emit_event: bool,
|
||||||
):
|
):
|
||||||
"""Append one execution record for an agent."""
|
"""Append one execution record for an agent."""
|
||||||
store = MetricsStore(_project_root(target), agent_name)
|
store = MetricsStore(_project_root(target), agent_name)
|
||||||
@@ -1092,6 +1121,21 @@ def metrics_record(
|
|||||||
|
|
||||||
if store.append(payload, idempotency_key=idempotency_key):
|
if store.append(payload, idempotency_key=idempotency_key):
|
||||||
click.echo(f"Recorded metrics for '{agent_name}'")
|
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:
|
else:
|
||||||
click.echo(
|
click.echo(
|
||||||
f"Skipped duplicate record for '{agent_name}' (idempotency key exists)"
|
f"Skipped duplicate record for '{agent_name}' (idempotency key exists)"
|
||||||
@@ -1412,8 +1456,39 @@ def schedule_validate(target: str):
|
|||||||
"--timezone", default="Europe/Berlin", show_default=True, help="Schedule timezone"
|
"--timezone", default="Europe/Berlin", show_default=True, help="Schedule timezone"
|
||||||
)
|
)
|
||||||
@click.option("--force", is_flag=True, help="Overwrite an existing schedule.yml")
|
@click.option("--force", is_flag=True, help="Overwrite an existing schedule.yml")
|
||||||
def schedule_init(target: str, timezone: str, force: bool):
|
@click.option(
|
||||||
"""Scaffold a default .kaizen/schedule.yml (coach + optimization weekly)."""
|
"--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))
|
path = schedule_path(_project_root(target))
|
||||||
|
|
||||||
if path.exists() and not force:
|
if path.exists() and not force:
|
||||||
@@ -1421,9 +1496,41 @@ def schedule_init(target: str, timezone: str, force: bool):
|
|||||||
click.echo(" Use --force to overwrite.")
|
click.echo(" Use --force to overwrite.")
|
||||||
return
|
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.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}")
|
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")
|
click.echo(" Validate with: kaizen-agentic schedule validate")
|
||||||
|
|
||||||
|
|
||||||
@@ -1565,6 +1672,156 @@ def _render_prepare_markdown(bundle: dict) -> str:
|
|||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
@cli.command("create-agent")
|
||||||
|
@click.argument("name")
|
||||||
|
@click.option(
|
||||||
|
"--category",
|
||||||
|
"-c",
|
||||||
|
type=click.Choice([c.value for c in AgentCategory]),
|
||||||
|
help="Agent category (prompted if omitted)",
|
||||||
|
)
|
||||||
|
@click.option("--description", "-d", help="One-line description (prompted if omitted)")
|
||||||
|
@click.option(
|
||||||
|
"--memory",
|
||||||
|
type=click.Choice(["enabled", "disabled"]),
|
||||||
|
default="enabled",
|
||||||
|
show_default=True,
|
||||||
|
help="Project memory support",
|
||||||
|
)
|
||||||
|
@click.option("--model", help="Optional model hint (e.g. claude-opus-4-8)")
|
||||||
|
@click.option(
|
||||||
|
"--target",
|
||||||
|
"-t",
|
||||||
|
default=".",
|
||||||
|
help="Project root containing agents/ (default: current)",
|
||||||
|
)
|
||||||
|
@click.option("--force", is_flag=True, help="Overwrite an existing agent file")
|
||||||
|
def create_agent(
|
||||||
|
name: str,
|
||||||
|
category: Optional[str],
|
||||||
|
description: Optional[str],
|
||||||
|
memory: str,
|
||||||
|
model: Optional[str],
|
||||||
|
target: str,
|
||||||
|
force: bool,
|
||||||
|
):
|
||||||
|
"""Scaffold a new schema-valid agent definition (agents/agent-<name>.md)."""
|
||||||
|
if not category:
|
||||||
|
category = click.prompt(
|
||||||
|
"Category", type=click.Choice([c.value for c in AgentCategory])
|
||||||
|
)
|
||||||
|
if not description:
|
||||||
|
description = click.prompt("One-line description")
|
||||||
|
|
||||||
|
agents_dir = _project_root(target) / "agents"
|
||||||
|
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
agent_path = agents_dir / f"agent-{name}.md"
|
||||||
|
|
||||||
|
if agent_path.exists() and not force:
|
||||||
|
click.echo(f"Agent already exists: {agent_path}")
|
||||||
|
click.echo(" Use --force to overwrite.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
frontmatter = [
|
||||||
|
"---",
|
||||||
|
f"name: {name}",
|
||||||
|
f"description: {description}",
|
||||||
|
f"category: {category}",
|
||||||
|
f"memory: {memory}",
|
||||||
|
]
|
||||||
|
if model:
|
||||||
|
frontmatter.append(f"model: {model}")
|
||||||
|
frontmatter.append("---")
|
||||||
|
|
||||||
|
title = name.replace("-", " ").title()
|
||||||
|
body = f"""
|
||||||
|
# {title} Agent
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
<!-- One paragraph: what this agent does and what it does not do. -->
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
<!-- Triggers and situations where this agent should be invoked. -->
|
||||||
|
|
||||||
|
## Instructions
|
||||||
|
|
||||||
|
<!-- Step-by-step guidance the agent follows. -->
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
<!-- What the agent produces and in what format. -->
|
||||||
|
"""
|
||||||
|
agent_path.write_text("\n".join(frontmatter) + "\n" + body)
|
||||||
|
|
||||||
|
# Validate the scaffold passes the frontmatter schema (T03).
|
||||||
|
errors = (
|
||||||
|
AgentRegistry(agents_dir).validate_frontmatter_schema().get(agent_path.name, [])
|
||||||
|
)
|
||||||
|
if errors:
|
||||||
|
click.echo(f"⚠️ Created {agent_path} but it has schema issues:")
|
||||||
|
for error in errors:
|
||||||
|
click.echo(f" ❌ {error}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
click.echo(f"✅ Created agent: {agent_path}")
|
||||||
|
click.echo(" Edit the skeleton, then validate: kaizen-agentic validate")
|
||||||
|
click.echo(" Before release: make agents-sync-package")
|
||||||
|
|
||||||
|
|
||||||
|
@cli.group()
|
||||||
|
def docs():
|
||||||
|
"""Generate project documentation from agent metadata."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@docs.command("generate")
|
||||||
|
@click.option("--target", "-t", default=".", help="Project root (default: current)")
|
||||||
|
@click.option(
|
||||||
|
"--check",
|
||||||
|
is_flag=True,
|
||||||
|
help="Exit non-zero if CLAUDE.md would change (do not write)",
|
||||||
|
)
|
||||||
|
def docs_generate(target: str, check: bool):
|
||||||
|
"""Refresh the '## Installed Agents' section of CLAUDE.md (idempotent)."""
|
||||||
|
target_path = _project_root(target)
|
||||||
|
# Resolve agents from the target project's own agents/ when present, so
|
||||||
|
# `docs generate --target other/project` documents that project's agents
|
||||||
|
# rather than the registry resolved from the current directory.
|
||||||
|
local_agents = target_path / "agents"
|
||||||
|
registry = AgentRegistry(local_agents) if local_agents.exists() else _get_registry()
|
||||||
|
installer = AgentInstaller(registry)
|
||||||
|
|
||||||
|
installed = installer.list_installed_agents(target_path)
|
||||||
|
if not installed:
|
||||||
|
click.echo("No agents installed in this project — nothing to document.")
|
||||||
|
click.echo(" Run: kaizen-agentic install <agents>")
|
||||||
|
return
|
||||||
|
|
||||||
|
agents = [a for a in (registry.get_agent(n) for n in installed) if a is not None]
|
||||||
|
section = render_installed_agents_section(agents)
|
||||||
|
|
||||||
|
claude_md = target_path / "CLAUDE.md"
|
||||||
|
current = claude_md.read_text() if claude_md.exists() else ""
|
||||||
|
updated = upsert_installed_agents_section(current, section)
|
||||||
|
|
||||||
|
if check:
|
||||||
|
if updated != current:
|
||||||
|
click.echo(f"❌ CLAUDE.md is out of date: {claude_md}")
|
||||||
|
click.echo(" Run: kaizen-agentic docs generate")
|
||||||
|
sys.exit(1)
|
||||||
|
click.echo(f"✅ CLAUDE.md is up to date ({len(agents)} agents)")
|
||||||
|
return
|
||||||
|
|
||||||
|
if updated == current:
|
||||||
|
click.echo(f"CLAUDE.md already up to date ({len(agents)} agents)")
|
||||||
|
return
|
||||||
|
|
||||||
|
claude_md.write_text(updated)
|
||||||
|
click.echo(f"Updated Installed Agents section ({len(agents)} agents): {claude_md}")
|
||||||
|
|
||||||
|
|
||||||
def _project_root(target: str) -> Path:
|
def _project_root(target: str) -> Path:
|
||||||
return Path(target).resolve()
|
return Path(target).resolve()
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,11 @@ from pathlib import Path
|
|||||||
from typing import List, Dict, Optional
|
from typing import List, Dict, Optional
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from .registry import AgentRegistry
|
from .registry import AgentRegistry, AgentDefinition
|
||||||
|
from .agent_docs import (
|
||||||
|
render_installed_agents_section,
|
||||||
|
upsert_installed_agents_section,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -82,7 +86,15 @@ class AgentInstaller:
|
|||||||
|
|
||||||
installed = []
|
installed = []
|
||||||
for agent_file in agents_dir.glob("agent-*.md"):
|
for agent_file in agents_dir.glob("agent-*.md"):
|
||||||
agent_name = agent_file.stem.replace("agent-", "")
|
# Prefer the frontmatter name (registry-authoritative); fall back to
|
||||||
|
# the filename when frontmatter is missing/unreadable. The filename
|
||||||
|
# encodes the category for a few agents (e.g. agent-project-
|
||||||
|
# management.md → name: project-assistant), so a pure filename derive
|
||||||
|
# produces names the registry cannot resolve (WP-0007 T02).
|
||||||
|
try:
|
||||||
|
agent_name = AgentDefinition._read_frontmatter(agent_file)["name"]
|
||||||
|
except Exception:
|
||||||
|
agent_name = agent_file.stem.replace("agent-", "")
|
||||||
installed.append(agent_name)
|
installed.append(agent_name)
|
||||||
|
|
||||||
return sorted(installed)
|
return sorted(installed)
|
||||||
@@ -235,60 +247,25 @@ agents-validate:
|
|||||||
try:
|
try:
|
||||||
claude_md = project_dir / "CLAUDE.md"
|
claude_md = project_dir / "CLAUDE.md"
|
||||||
|
|
||||||
agent_section = "## Installed Agents\n\n"
|
agents = [
|
||||||
agent_section += (
|
agent
|
||||||
"This project includes the following specialized agents:\n\n"
|
for agent in (self.registry.get_agent(name) for name in agent_names)
|
||||||
)
|
if agent is not None
|
||||||
|
]
|
||||||
|
agent_section = render_installed_agents_section(agents)
|
||||||
|
|
||||||
# Group agents by category
|
# Update or create CLAUDE.md (idempotent upsert — WP-0007 T01/T02)
|
||||||
categories = {}
|
|
||||||
for agent_name in agent_names:
|
|
||||||
agent = self.registry.get_agent(agent_name)
|
|
||||||
if agent:
|
|
||||||
category = agent.category.value
|
|
||||||
if category not in categories:
|
|
||||||
categories[category] = []
|
|
||||||
categories[category].append(agent)
|
|
||||||
|
|
||||||
# Generate documentation
|
|
||||||
for category, agents in categories.items():
|
|
||||||
agent_section += f"### {category.replace('-', ' ').title()}\n\n"
|
|
||||||
for agent in agents:
|
|
||||||
agent_section += f"- **{agent.name}**: {agent.description}\n"
|
|
||||||
agent_section += "\n"
|
|
||||||
|
|
||||||
agent_section += (
|
|
||||||
"Use these agents by referencing them in your "
|
|
||||||
"Claude Code interactions.\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Update or create CLAUDE.md
|
|
||||||
if claude_md.exists():
|
if claude_md.exists():
|
||||||
with open(claude_md, "r") as f:
|
content = claude_md.read_text()
|
||||||
content = f.read()
|
content = upsert_installed_agents_section(content, agent_section)
|
||||||
|
claude_md.write_text(content)
|
||||||
# Replace existing agent section or append
|
|
||||||
if "## Installed Agents" in content:
|
|
||||||
import re
|
|
||||||
|
|
||||||
content = re.sub(
|
|
||||||
r"## Installed Agents.*?(?=##|\Z)",
|
|
||||||
agent_section,
|
|
||||||
content,
|
|
||||||
flags=re.DOTALL,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
content += "\n" + agent_section
|
|
||||||
|
|
||||||
with open(claude_md, "w") as f:
|
|
||||||
f.write(content)
|
|
||||||
else:
|
else:
|
||||||
# Create new CLAUDE.md
|
|
||||||
header = "# Claude Code Configuration\n\n"
|
header = "# Claude Code Configuration\n\n"
|
||||||
header += "This file contains Claude Code configuration and agent information.\n\n"
|
header += (
|
||||||
|
"This file contains Claude Code configuration and agent "
|
||||||
with open(claude_md, "w") as f:
|
"information.\n\n"
|
||||||
f.write(header + agent_section)
|
)
|
||||||
|
claude_md.write_text(header + agent_section)
|
||||||
|
|
||||||
print(f"Updated documentation: {claude_md}")
|
print(f"Updated documentation: {claude_md}")
|
||||||
|
|
||||||
|
|||||||
@@ -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 .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
|
from .helix import HelixCorrelationAdapter, enrich_helix_correlation
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"HelixCorrelationAdapter",
|
"HelixCorrelationAdapter",
|
||||||
|
"build_metrics_recorded_envelope",
|
||||||
"enrich_helix_correlation",
|
"enrich_helix_correlation",
|
||||||
|
"publish_metrics_recorded_event",
|
||||||
"publish_optimizer_evidence",
|
"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
|
||||||
@@ -60,8 +60,10 @@ class AgentDefinition:
|
|||||||
# Extract dependencies from frontmatter and content
|
# Extract dependencies from frontmatter and content
|
||||||
dependencies = cls._extract_dependencies(content, frontmatter)
|
dependencies = cls._extract_dependencies(content, frontmatter)
|
||||||
|
|
||||||
# Determine category from name or content
|
# The declared frontmatter category is authoritative when it is a known
|
||||||
category = cls._determine_category(frontmatter["name"], content)
|
# value (it is what `validate` enforces, WP-0007 T03); fall back to the
|
||||||
|
# name/content heuristic only when absent or unrecognised.
|
||||||
|
category = cls._resolve_category(frontmatter, content)
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
name=frontmatter["name"],
|
name=frontmatter["name"],
|
||||||
@@ -118,6 +120,17 @@ class AgentDefinition:
|
|||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _resolve_category(cls, frontmatter: dict, content: str) -> AgentCategory:
|
||||||
|
"""Prefer the declared frontmatter category; heuristic as fallback."""
|
||||||
|
declared = frontmatter.get("category")
|
||||||
|
if isinstance(declared, str):
|
||||||
|
try:
|
||||||
|
return AgentCategory(declared.strip())
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return cls._determine_category(frontmatter["name"], content)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _determine_category(name: str, content: str) -> AgentCategory:
|
def _determine_category(name: str, content: str) -> AgentCategory:
|
||||||
"""Determine agent category based on name and content."""
|
"""Determine agent category based on name and content."""
|
||||||
@@ -288,6 +301,65 @@ class AgentRegistry:
|
|||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
def validate_frontmatter_schema(self) -> Dict[str, List[str]]:
|
||||||
|
"""Validate each agent file's frontmatter against the required schema.
|
||||||
|
|
||||||
|
Required: ``name``, ``description``, ``category`` (non-empty strings);
|
||||||
|
``category`` must be a known :class:`AgentCategory` value. Optional:
|
||||||
|
``memory`` ∈ {enabled, disabled}; ``model`` a non-empty string. Keyed by
|
||||||
|
filename so files with a missing/invalid ``name`` still surface.
|
||||||
|
"""
|
||||||
|
errors: Dict[str, List[str]] = {}
|
||||||
|
valid_categories = {c.value for c in AgentCategory}
|
||||||
|
|
||||||
|
if not self.agents_dir.exists():
|
||||||
|
return errors
|
||||||
|
|
||||||
|
for agent_file in sorted(self.agents_dir.glob("agent-*.md")):
|
||||||
|
file_errors: List[str] = []
|
||||||
|
try:
|
||||||
|
content = agent_file.read_text(encoding="utf-8")
|
||||||
|
match = re.match(r"^---\n(.*?)\n---\n", content, re.DOTALL)
|
||||||
|
if not match:
|
||||||
|
errors[agent_file.name] = ["missing YAML frontmatter"]
|
||||||
|
continue
|
||||||
|
frontmatter = yaml.safe_load(match.group(1))
|
||||||
|
if not isinstance(frontmatter, dict):
|
||||||
|
errors[agent_file.name] = ["frontmatter is not a mapping"]
|
||||||
|
continue
|
||||||
|
|
||||||
|
for field in ("name", "description", "category"):
|
||||||
|
value = frontmatter.get(field)
|
||||||
|
if not isinstance(value, str) or not value.strip():
|
||||||
|
file_errors.append(f"missing or empty required field: {field}")
|
||||||
|
|
||||||
|
category = frontmatter.get("category")
|
||||||
|
if isinstance(category, str) and category not in valid_categories:
|
||||||
|
file_errors.append(
|
||||||
|
f"invalid category '{category}' (expected one of "
|
||||||
|
f"{', '.join(sorted(valid_categories))})"
|
||||||
|
)
|
||||||
|
|
||||||
|
memory = frontmatter.get("memory")
|
||||||
|
if memory is not None and memory not in ("enabled", "disabled"):
|
||||||
|
file_errors.append(
|
||||||
|
f"invalid memory '{memory}' "
|
||||||
|
"(expected 'enabled' or 'disabled')"
|
||||||
|
)
|
||||||
|
|
||||||
|
model = frontmatter.get("model")
|
||||||
|
if model is not None and (
|
||||||
|
not isinstance(model, str) or not model.strip()
|
||||||
|
):
|
||||||
|
file_errors.append("model must be a non-empty string when present")
|
||||||
|
except Exception as exc: # pragma: no cover - defensive
|
||||||
|
file_errors.append(f"failed to parse frontmatter: {exc}")
|
||||||
|
|
||||||
|
if file_errors:
|
||||||
|
errors[agent_file.name] = file_errors
|
||||||
|
|
||||||
|
return errors
|
||||||
|
|
||||||
def _has_circular_dependency(
|
def _has_circular_dependency(
|
||||||
self, agent_name: str, visited: Optional[Set[str]] = None
|
self, agent_name: str, visited: Optional[Set[str]] = None
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
|||||||
@@ -27,6 +27,29 @@ DEFAULT_AGENTS: Dict[str, Dict[str, Any]] = {
|
|||||||
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
"tdd-workflow": {"cadence": "monthly", "enabled": False},
|
||||||
}
|
}
|
||||||
DEFAULT_TIMEZONE = "Europe/Berlin"
|
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):
|
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)
|
body = yaml.safe_dump(document, sort_keys=False, default_flow_style=False)
|
||||||
return header + body
|
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
|
||||||
|
|||||||
97
tests/test_agent_docs.py
Normal file
97
tests/test_agent_docs.py
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
"""Tests for idempotent agent-docs generation (WP-0007 T01/T02)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from kaizen_agentic.agent_docs import (
|
||||||
|
SECTION_FOOTER,
|
||||||
|
SECTION_HEADING,
|
||||||
|
render_installed_agents_section,
|
||||||
|
upsert_installed_agents_section,
|
||||||
|
)
|
||||||
|
from kaizen_agentic.cli import cli
|
||||||
|
from kaizen_agentic.registry import AgentRegistry
|
||||||
|
|
||||||
|
AGENTS_DIR = Path(__file__).parent.parent / "agents"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner() -> CliRunner:
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def _section() -> str:
|
||||||
|
agents = AgentRegistry(AGENTS_DIR).list_agents()[:4]
|
||||||
|
return render_installed_agents_section(agents)
|
||||||
|
|
||||||
|
|
||||||
|
class TestUpsertIdempotency:
|
||||||
|
def test_upsert_is_idempotent(self):
|
||||||
|
section = _section()
|
||||||
|
base = f"# Project\n\nintro\n\n{section}\n## Keep Me\nbody\n"
|
||||||
|
once = upsert_installed_agents_section(base, section)
|
||||||
|
twice = upsert_installed_agents_section(once, section)
|
||||||
|
assert once == twice
|
||||||
|
assert once.count(SECTION_HEADING) == 1
|
||||||
|
assert once.count(SECTION_FOOTER) == 1
|
||||||
|
# A following top-level section must survive the replace
|
||||||
|
assert once.count("## Keep Me") == 1
|
||||||
|
|
||||||
|
def test_subheadings_do_not_truncate_section(self):
|
||||||
|
section = _section()
|
||||||
|
# The block contains '### Category' subheadings; the replace must not
|
||||||
|
# stop at the first one (the original regex bug).
|
||||||
|
merged = upsert_installed_agents_section("# P\n\n", section)
|
||||||
|
assert merged.count(SECTION_FOOTER) == 1
|
||||||
|
assert "### " in merged # categories rendered
|
||||||
|
|
||||||
|
def test_append_when_absent(self):
|
||||||
|
section = _section()
|
||||||
|
merged = upsert_installed_agents_section("# Project\n\nbody\n", section)
|
||||||
|
assert SECTION_HEADING in merged
|
||||||
|
assert merged.count(SECTION_FOOTER) == 1
|
||||||
|
|
||||||
|
|
||||||
|
class TestDocsGenerateCli:
|
||||||
|
def test_generate_then_check_clean(self, runner: CliRunner, tmp_path: Path):
|
||||||
|
# A project with two installed agents
|
||||||
|
proj = tmp_path / "proj"
|
||||||
|
(proj / "agents").mkdir(parents=True)
|
||||||
|
for name, cat in (("alpha", "testing"), ("beta", "code-quality")):
|
||||||
|
(proj / "agents" / f"agent-{name}.md").write_text(
|
||||||
|
f"---\nname: {name}\ndescription: d{name}\ncategory: {cat}\n---\nx\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
gen = runner.invoke(cli, ["docs", "generate", "--target", str(proj)])
|
||||||
|
assert gen.exit_code == 0
|
||||||
|
claude = (proj / "CLAUDE.md").read_text()
|
||||||
|
assert claude.count(SECTION_HEADING) == 1
|
||||||
|
assert "**alpha**" in claude and "**beta**" in claude
|
||||||
|
|
||||||
|
# Second generate is a no-op
|
||||||
|
again = runner.invoke(cli, ["docs", "generate", "--target", str(proj)])
|
||||||
|
assert "already up to date" in again.output
|
||||||
|
|
||||||
|
# --check passes on a synced repo
|
||||||
|
check = runner.invoke(
|
||||||
|
cli, ["docs", "generate", "--check", "--target", str(proj)]
|
||||||
|
)
|
||||||
|
assert check.exit_code == 0
|
||||||
|
assert "up to date" in check.output
|
||||||
|
|
||||||
|
def test_check_fails_when_stale(self, runner: CliRunner, tmp_path: Path):
|
||||||
|
proj = tmp_path / "proj"
|
||||||
|
(proj / "agents").mkdir(parents=True)
|
||||||
|
(proj / "agents" / "agent-alpha.md").write_text(
|
||||||
|
"---\nname: alpha\ndescription: d\ncategory: testing\n---\nx\n"
|
||||||
|
)
|
||||||
|
(proj / "CLAUDE.md").write_text("# Proj\n\nno agents section yet\n")
|
||||||
|
check = runner.invoke(
|
||||||
|
cli, ["docs", "generate", "--check", "--target", str(proj)]
|
||||||
|
)
|
||||||
|
assert check.exit_code == 1
|
||||||
|
assert "out of date" in check.output
|
||||||
@@ -40,10 +40,15 @@ class TestClickWorkaround:
|
|||||||
assert "Got unexpected extra argument" not in stdout_content
|
assert "Got unexpected extra argument" not in stdout_content
|
||||||
assert "Got unexpected extra argument" not in stderr_content
|
assert "Got unexpected extra argument" not in stderr_content
|
||||||
|
|
||||||
def test_update_command_error_suppression(self):
|
def test_update_command_error_suppression(self, tmp_path):
|
||||||
"""Test that spurious 'unexpected extra argument' errors are suppressed for update commands."""
|
"""Test that spurious 'unexpected extra argument' errors are suppressed for update commands."""
|
||||||
|
# Seed a temp project so `update` does not rewrite the repo's own agents/
|
||||||
|
(tmp_path / "agents").mkdir()
|
||||||
|
(tmp_path / "agents" / "agent-tdd-workflow.md").write_text(
|
||||||
|
"---\nname: tdd-workflow\ndescription: d\ncategory: testing\n---\nx\n"
|
||||||
|
)
|
||||||
# Test the update command that also shows spurious errors
|
# Test the update command that also shows spurious errors
|
||||||
with patch("sys.argv", ["kaizen-agentic", "update"]):
|
with patch("sys.argv", ["kaizen-agentic", "update", "--target", str(tmp_path)]):
|
||||||
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
||||||
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
|
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
|
||||||
try:
|
try:
|
||||||
@@ -116,9 +121,12 @@ class TestClickWorkaround:
|
|||||||
class TestInstallCommandSpecifics:
|
class TestInstallCommandSpecifics:
|
||||||
"""Test specific install command scenarios."""
|
"""Test specific install command scenarios."""
|
||||||
|
|
||||||
def test_install_with_valid_agent(self):
|
def test_install_with_valid_agent(self, tmp_path):
|
||||||
"""Test install command with a valid agent name."""
|
"""Test install command with a valid agent name."""
|
||||||
with patch("sys.argv", ["kaizen-agentic", "install", "tdd-workflow"]):
|
with patch(
|
||||||
|
"sys.argv",
|
||||||
|
["kaizen-agentic", "install", "tdd-workflow", "--target", str(tmp_path)],
|
||||||
|
):
|
||||||
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
with patch("sys.stdout", new_callable=StringIO) as mock_stdout:
|
||||||
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
|
with patch("sys.stderr", new_callable=StringIO) as mock_stderr:
|
||||||
try:
|
try:
|
||||||
|
|||||||
100
tests/test_create_agent.py
Normal file
100
tests/test_create_agent.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
"""Tests for the create-agent scaffold (WP-0007 T04)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from click.testing import CliRunner
|
||||||
|
|
||||||
|
from kaizen_agentic.cli import cli
|
||||||
|
from kaizen_agentic.registry import AgentRegistry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner() -> CliRunner:
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
class TestCreateAgent:
|
||||||
|
def test_scaffold_is_schema_valid_and_loads(
|
||||||
|
self, runner: CliRunner, tmp_path: Path
|
||||||
|
):
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"create-agent",
|
||||||
|
"demo-helper",
|
||||||
|
"-c",
|
||||||
|
"testing",
|
||||||
|
"-d",
|
||||||
|
"Demo helper for tests",
|
||||||
|
"--target",
|
||||||
|
str(tmp_path),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
agent_path = tmp_path / "agents" / "agent-demo-helper.md"
|
||||||
|
assert agent_path.exists()
|
||||||
|
|
||||||
|
registry = AgentRegistry(tmp_path / "agents")
|
||||||
|
# Passes the schema and is loadable by the registry.
|
||||||
|
assert registry.validate_frontmatter_schema() == {}
|
||||||
|
agent = registry.get_agent("demo-helper")
|
||||||
|
assert agent is not None
|
||||||
|
assert agent.category.value == "testing"
|
||||||
|
assert agent.memory == "enabled"
|
||||||
|
|
||||||
|
def test_interactive_prompts_when_flags_missing(
|
||||||
|
self, runner: CliRunner, tmp_path: Path
|
||||||
|
):
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
["create-agent", "prompted", "--target", str(tmp_path)],
|
||||||
|
input="testing\nA prompted agent\n",
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
content = (tmp_path / "agents" / "agent-prompted.md").read_text()
|
||||||
|
assert "category: testing" in content
|
||||||
|
assert "description: A prompted agent" in content
|
||||||
|
|
||||||
|
def test_refuses_overwrite_without_force(self, runner: CliRunner, tmp_path: Path):
|
||||||
|
args = [
|
||||||
|
"create-agent",
|
||||||
|
"dup",
|
||||||
|
"-c",
|
||||||
|
"meta",
|
||||||
|
"-d",
|
||||||
|
"first",
|
||||||
|
"--target",
|
||||||
|
str(tmp_path),
|
||||||
|
]
|
||||||
|
assert runner.invoke(cli, args).exit_code == 0
|
||||||
|
second = runner.invoke(cli, args)
|
||||||
|
assert second.exit_code == 1
|
||||||
|
assert "already exists" in second.output
|
||||||
|
|
||||||
|
def test_force_overwrites(self, runner: CliRunner, tmp_path: Path):
|
||||||
|
base = ["create-agent", "dup", "--target", str(tmp_path)]
|
||||||
|
runner.invoke(cli, base + ["-c", "meta", "-d", "first"])
|
||||||
|
result = runner.invoke(cli, base + ["-c", "meta", "-d", "second", "--force"])
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert (
|
||||||
|
"description: second" in (tmp_path / "agents" / "agent-dup.md").read_text()
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_rejects_invalid_category(self, runner: CliRunner, tmp_path: Path):
|
||||||
|
result = runner.invoke(
|
||||||
|
cli,
|
||||||
|
[
|
||||||
|
"create-agent",
|
||||||
|
"x",
|
||||||
|
"-c",
|
||||||
|
"nonsense",
|
||||||
|
"-d",
|
||||||
|
"d",
|
||||||
|
"--target",
|
||||||
|
str(tmp_path),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert result.exit_code != 0
|
||||||
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
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import yaml
|
||||||
from click.testing import CliRunner
|
from click.testing import CliRunner
|
||||||
|
|
||||||
from kaizen_agentic.cli import cli
|
from kaizen_agentic.cli import cli
|
||||||
from kaizen_agentic.schedule import (
|
from kaizen_agentic.schedule import (
|
||||||
ScheduleError,
|
ScheduleError,
|
||||||
|
engagement_schedule_yaml,
|
||||||
parse_schedule,
|
parse_schedule,
|
||||||
schedule_path,
|
schedule_path,
|
||||||
validate_schedule,
|
validate_schedule,
|
||||||
@@ -30,6 +32,20 @@ def project_dir(tmp_path: Path) -> Path:
|
|||||||
|
|
||||||
|
|
||||||
class TestScheduleModule:
|
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):
|
def test_parse_requires_version(self):
|
||||||
with pytest.raises(ScheduleError):
|
with pytest.raises(ScheduleError):
|
||||||
parse_schedule({"agents": {}})
|
parse_schedule({"agents": {}})
|
||||||
@@ -67,6 +83,67 @@ class TestScheduleCli:
|
|||||||
assert path.exists()
|
assert path.exists()
|
||||||
assert "coach" in path.read_text()
|
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(
|
def test_init_no_overwrite_without_force(
|
||||||
self, runner: CliRunner, project_dir: Path
|
self, runner: CliRunner, project_dir: Path
|
||||||
):
|
):
|
||||||
|
|||||||
74
tests/test_validate_schema.py
Normal file
74
tests/test_validate_schema.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
"""Tests for agent frontmatter schema validation (WP-0007 T03)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from kaizen_agentic.registry import AgentRegistry
|
||||||
|
|
||||||
|
REPO_AGENTS = Path(__file__).parent.parent / "agents"
|
||||||
|
|
||||||
|
|
||||||
|
def _registry(tmp_path: Path, files: dict) -> AgentRegistry:
|
||||||
|
agents = tmp_path / "agents"
|
||||||
|
agents.mkdir(parents=True)
|
||||||
|
for filename, content in files.items():
|
||||||
|
(agents / filename).write_text(content)
|
||||||
|
return AgentRegistry(agents)
|
||||||
|
|
||||||
|
|
||||||
|
class TestFrontmatterSchema:
|
||||||
|
def test_repo_agents_are_schema_valid(self):
|
||||||
|
# The shipped agents/ must always pass the schema.
|
||||||
|
assert AgentRegistry(REPO_AGENTS).validate_frontmatter_schema() == {}
|
||||||
|
|
||||||
|
def test_good_agent_passes(self, tmp_path: Path):
|
||||||
|
reg = _registry(
|
||||||
|
tmp_path,
|
||||||
|
{
|
||||||
|
"agent-good.md": (
|
||||||
|
"---\nname: good\ndescription: A fine agent\n"
|
||||||
|
"category: testing\nmemory: enabled\n---\nbody\n"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
assert reg.validate_frontmatter_schema() == {}
|
||||||
|
|
||||||
|
def test_missing_required_fields(self, tmp_path: Path):
|
||||||
|
reg = _registry(
|
||||||
|
tmp_path,
|
||||||
|
{"agent-x.md": "---\nname: x\ncategory: testing\n---\nbody\n"},
|
||||||
|
)
|
||||||
|
errors = reg.validate_frontmatter_schema()["agent-x.md"]
|
||||||
|
assert any("description" in e for e in errors)
|
||||||
|
|
||||||
|
def test_invalid_category(self, tmp_path: Path):
|
||||||
|
reg = _registry(
|
||||||
|
tmp_path,
|
||||||
|
{
|
||||||
|
"agent-x.md": (
|
||||||
|
"---\nname: x\ndescription: d\ncategory: nonsense\n---\nb\n"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
errors = reg.validate_frontmatter_schema()["agent-x.md"]
|
||||||
|
assert any("invalid category" in e for e in errors)
|
||||||
|
|
||||||
|
def test_invalid_memory(self, tmp_path: Path):
|
||||||
|
reg = _registry(
|
||||||
|
tmp_path,
|
||||||
|
{
|
||||||
|
"agent-x.md": (
|
||||||
|
"---\nname: x\ndescription: d\ncategory: testing\n"
|
||||||
|
"memory: maybe\n---\nb\n"
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
errors = reg.validate_frontmatter_schema()["agent-x.md"]
|
||||||
|
assert any("invalid memory" in e for e in errors)
|
||||||
|
|
||||||
|
def test_missing_frontmatter(self, tmp_path: Path):
|
||||||
|
reg = _registry(tmp_path, {"agent-x.md": "just text, no frontmatter\n"})
|
||||||
|
assert reg.validate_frontmatter_schema()["agent-x.md"] == [
|
||||||
|
"missing YAML frontmatter"
|
||||||
|
]
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
---
|
||||||
|
id: KAIZEN-WP-0007
|
||||||
|
type: workplan
|
||||||
|
title: "Agent Authoring & Doc Generation (v1.4.0)"
|
||||||
|
domain: custodian
|
||||||
|
repo: kaizen-agentic
|
||||||
|
status: done
|
||||||
|
owner: kaizen-agentic
|
||||||
|
topic_slug: custodian
|
||||||
|
state_hub_workstream_id: a8bc88a4-0ee3-44c6-aff5-9d7f54a316f5
|
||||||
|
depends_on:
|
||||||
|
- KAIZEN-WP-0006
|
||||||
|
created: "2026-06-18"
|
||||||
|
updated: "2026-06-18"
|
||||||
|
tasks:
|
||||||
|
- id: T01
|
||||||
|
state_hub_task_id: debaf0ac-47df-4bbd-aa1f-96c7b96a64ed
|
||||||
|
status: done
|
||||||
|
title: Fix idempotent CLAUDE.md doc regeneration (installer regex bug)
|
||||||
|
- id: T02
|
||||||
|
state_hub_task_id: e2d9bea8-243b-4b12-bba3-4d43a3bae71c
|
||||||
|
status: done
|
||||||
|
title: Reusable agent-docs module and kaizen-agentic docs generate command
|
||||||
|
- id: T03
|
||||||
|
state_hub_task_id: 57176887-5344-444c-aa5a-a4aea161ee71
|
||||||
|
status: done
|
||||||
|
title: Enforce agent frontmatter schema in validate
|
||||||
|
- id: T04
|
||||||
|
state_hub_task_id: 724e3862-602b-4ef0-88b8-ddb78a225046
|
||||||
|
status: done
|
||||||
|
title: kaizen-agentic create-agent scaffold for new agents
|
||||||
|
- id: T05
|
||||||
|
state_hub_task_id: 4d6e323c-9cad-49ce-9356-9192c20cc986
|
||||||
|
status: done
|
||||||
|
title: Unit tests for docs generate, schema validation, create-agent
|
||||||
|
- id: T06
|
||||||
|
state_hub_task_id: 6715aa6f-1ee0-4f22-9249-f1cd41763cd1
|
||||||
|
status: done
|
||||||
|
title: Docs, CLI cheat sheet, CHANGELOG for v1.4.0
|
||||||
|
---
|
||||||
|
|
||||||
|
# KAIZEN-WP-0007 — Agent Authoring & Doc Generation
|
||||||
|
|
||||||
|
**Status:** done
|
||||||
|
**Owner:** kaizen-agentic
|
||||||
|
**Repo:** kaizen-agentic
|
||||||
|
**Target version:** 1.4.0
|
||||||
|
**Depends on:** WP-0006 (scheduled agent execution)
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Close the WP-0005/WP-0006 deferrals — **agent selection/authoring**, **template
|
||||||
|
schema enforcement**, and **doc generation** — and fix the doc-generation defect
|
||||||
|
they exposed. After this workplan, authoring a new agent and keeping project
|
||||||
|
docs in sync is a first-class, idempotent, validated CLI flow.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
`AgentInstaller._update_documentation()` regenerates the `## Installed Agents`
|
||||||
|
block in a project's `CLAUDE.md`, but its regex
|
||||||
|
(`## Installed Agents.*?(?=##|\Z)`) is non-greedy and the `(?=##)` lookahead
|
||||||
|
matches `### Category` subheadings *inside* the section — so each run leaves the
|
||||||
|
old category lists and footer in place and appends a fresh copy. The block
|
||||||
|
duplicates and grows unbounded (reported to state-hub; the corrupting write is
|
||||||
|
ours). `validate` only checks dependencies and file existence — it does not
|
||||||
|
enforce the agent frontmatter schema — and there is no scaffolding command for
|
||||||
|
new agents.
|
||||||
|
|
||||||
|
## Tasks
|
||||||
|
|
||||||
|
### T01 — Fix idempotent doc regeneration
|
||||||
|
- Anchor the replace to a top-level heading: `(?=\n## (?!#)|\Z)`.
|
||||||
|
- Add a regression test that runs regeneration twice and asserts exactly one
|
||||||
|
`## Installed Agents`, one footer, one block per category.
|
||||||
|
- Clean the existing baseline duplication in this repo's `CLAUDE.md`.
|
||||||
|
|
||||||
|
### T02 — Reusable agent-docs module + `docs generate`
|
||||||
|
- Extract the "Installed Agents" rendering into a pure function
|
||||||
|
(`render_installed_agents_section`) and an idempotent
|
||||||
|
`upsert_installed_agents_section(content, section)`.
|
||||||
|
- `_update_documentation` reuses them (no behavioral fork).
|
||||||
|
- New `kaizen-agentic docs generate [--target PATH] [--check]` refreshes the
|
||||||
|
section for installed agents; `--check` exits non-zero if it would change
|
||||||
|
(CI-friendly).
|
||||||
|
|
||||||
|
### T03 — Enforce agent frontmatter schema in `validate`
|
||||||
|
- Add `validate_frontmatter_schema()` to the registry: required `name`,
|
||||||
|
`description`, `category`; `category` ∈ `AgentCategory`; `memory` ∈
|
||||||
|
{`enabled`,`disabled`} when present; `model` a non-empty string when present.
|
||||||
|
- Surface schema errors in `kaizen-agentic validate` alongside dependency
|
||||||
|
errors, with actionable messages.
|
||||||
|
|
||||||
|
### T04 — `create-agent` scaffold
|
||||||
|
- `kaizen-agentic create-agent <name> [--category] [--description] [--memory]
|
||||||
|
[--target] [--force]` writes a schema-valid `agents/agent-<name>.md` with
|
||||||
|
frontmatter + section skeleton.
|
||||||
|
- Missing `--category`/`--description` fall back to interactive prompts.
|
||||||
|
- Refuses invalid category; refuses overwrite without `--force`; output passes
|
||||||
|
T03 validation.
|
||||||
|
|
||||||
|
### T05 — Tests
|
||||||
|
- `tests/test_agent_docs.py` — idempotency, `docs generate --check`.
|
||||||
|
- `tests/test_validate_schema.py` — schema pass/fail cases.
|
||||||
|
- `tests/test_create_agent.py` — scaffold + round-trips through registry.
|
||||||
|
|
||||||
|
### T06 — Docs
|
||||||
|
- `docs/CLI_CHEAT_SHEET.md` — `docs generate`, `create-agent`, schema validate.
|
||||||
|
- `docs/agency-framework.md` — authoring + doc-sync note.
|
||||||
|
- `CHANGELOG.md` `[Unreleased]`; `TODO.md` pointer.
|
||||||
|
|
||||||
|
## Definition of Done
|
||||||
|
|
||||||
|
- Doc regeneration is idempotent (N runs == 1 run); baseline cleaned.
|
||||||
|
- `kaizen-agentic validate` fails on malformed agent frontmatter with clear errors.
|
||||||
|
- `kaizen-agentic create-agent` produces an agent that passes `validate`.
|
||||||
|
- `kaizen-agentic docs generate --check` is green on a synced repo.
|
||||||
|
- Full test suite + `make release-check` green.
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- Multi-file agent packages / protocol scaffolding (separate workplan).
|
||||||
|
- Publishing generated docs to external knowledge bases (info-tech-canon).
|
||||||
|
- Changing the agent frontmatter schema itself (only enforcing the current one).
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Boundary: the doc-regeneration *trigger* (a state-hub routine invoking the
|
||||||
|
installer mid-session) is tracked separately in the state-hub inbox; this
|
||||||
|
workplan fixes the *write* so the trigger becomes harmless/idempotent.
|
||||||
|
- `create-agent` writes to `agents/`; remember `make agents-sync-package` before
|
||||||
|
release so packaged `data/agents/` parity holds (existing release-check gate).
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
---
|
||||||
|
id: KAIZEN-WP-0008
|
||||||
|
type: workplan
|
||||||
|
title: "Coulomb-loop supplier engagement (customer-repo playbook)"
|
||||||
|
domain: custodian
|
||||||
|
repo: kaizen-agentic
|
||||||
|
status: active
|
||||||
|
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: todo
|
||||||
|
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: todo
|
||||||
|
title: Draft ADR-006 customer engagement convention
|
||||||
|
- id: T07
|
||||||
|
status: todo
|
||||||
|
title: Absorb coulomb-loop supplier-notes into playbook v1
|
||||||
|
- id: T08
|
||||||
|
status: todo
|
||||||
|
title: ActivityDefinition override manifest design for hybrid sync
|
||||||
|
- id: T09
|
||||||
|
status: todo
|
||||||
|
title: Tests for emit-event and engagement init
|
||||||
|
- id: T10
|
||||||
|
status: todo
|
||||||
|
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:** active
|
||||||
|
**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: todo
|
||||||
|
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: todo
|
||||||
|
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: todo
|
||||||
|
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: todo
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "f45077ea-5d24-4a85-bac2-ab9a3f61c20b"
|
||||||
|
```
|
||||||
|
|
||||||
|
Unit tests: `--emit-event` payload shape; `--engagement` schedule output.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3 — Playbook v1 and hybrid sync design
|
||||||
|
|
||||||
|
## Absorb supplier-notes into playbook v1
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: KAIZEN-WP-0008-T07
|
||||||
|
status: todo
|
||||||
|
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: todo
|
||||||
|
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: todo
|
||||||
|
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