Compare commits

...

5 Commits

Author SHA1 Message Date
93bf49479b feat: schedule init --engagement for customer bootstrap presets
Some checks failed
ci / test (push) Has been cancelled
Add --engagement, --agents, and --bootstrap-cadence flags to scaffold
hourly/daily/weekly engagement schedules. Hourly bootstrap keeps
cadence: daily with hourly cron overrides per coulomb-loop ADR-003.
Document activity-core requirements in activity-core-handoff-engagement.md.
Closes KAIZEN-WP-0008 T02 and T04.
2026-06-18 08:59:45 +02:00
1641a3165d feat: metrics record --emit-event for kaizen.metrics.recorded
Publish activity-core EventEnvelope payloads to NATS subject
activity.kaizen.metrics.recorded after a successful append.
Optional nats-py via kaizen-agentic[events]; project slug from
KAIZEN_PROJECT_SLUG or directory basename. Skips emit on
idempotency duplicates. Closes KAIZEN-WP-0008 T03.
2026-06-18 08:53:36 +02:00
c5798f58e4 docs: fix supplier ActivityDefinition rule path syntax
Use context.* field paths and {context.*} placeholders to match
activity-core rule evaluator conventions.
2026-06-18 08:11:14 +02:00
7424893758 Mark discover_kaizen_scheduled_repos as implemented in activity-core. 2026-06-18 07:46:46 +02:00
d220bae007 Add coulomb-loop pilot schedule opt-in and supplier engagement workplan.
Commit ADR-005 schedule manifest for hourly bootstrap phase and KAIZEN-WP-0008
to support the coulomb-loop customer engagement.
2026-06-18 04:54:34 +02:00
24 changed files with 1157 additions and 24 deletions

View File

@@ -2,12 +2,23 @@
# Custodian Brief — kaizen-agentic
**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)*
## 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)

17
.kaizen/schedule.yml Normal file
View 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

View File

@@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [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

View File

@@ -112,6 +112,8 @@ Session-close template: `docs/templates/session-close-protocol.md`
# Opt this repo into fleet scheduling
kaizen-agentic schedule init # coach + optimization weekly
kaizen-agentic schedule init --timezone UTC # override timezone
kaizen-agentic schedule init --engagement coulomb-loop \
--agents coach,optimization --bootstrap-cadence hourly
kaizen-agentic schedule validate # schema + agent-name checks
kaizen-agentic schedule list # enabled entries (--all incl. disabled)

View File

@@ -37,7 +37,7 @@ invoke kaizen-agentic CLI commands.
|------------|---------|-------------|
| [weekly-metrics-optimize](integrations/activity-definitions/weekly-metrics-optimize.md) | Cron Mon 08:00 | `metrics optimize` |
| [post-install-metrics-scaffold](integrations/activity-definitions/post-install-metrics-scaffold.md) | `kaizen.agent.installed` | `memory init` validation |
| [low-success-rate-review](integrations/activity-definitions/low-success-rate-review.md) | `kaizen.metrics.recorded` | `metrics show` + `optimize` |
| [low-success-rate-review](integrations/activity-definitions/low-success-rate-review.md) | `kaizen.metrics.recorded` | `metrics record --emit-event` |
**Activation handoff (activity-core owners):**

View File

@@ -17,6 +17,7 @@ Memory-enabled agents record per-session outcomes at close:
```bash
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1>
kaizen-agentic metrics record <agent> --success --time <s> --quality <0-1> --emit-event
kaizen-agentic metrics optimize [agent]
kaizen-agentic memory brief <agent> # includes Performance Summary
```
@@ -45,4 +46,4 @@ fleet layers above satisfy INTENT's "measurable agents" requirement without tele
## Feedback loop
User experience feedback uses [FEEDBACK.md](FEEDBACK.md) and Gitea issue templates —
separate from execution metrics.
separate from execution metrics.

View File

@@ -115,6 +115,7 @@ embed it in a task `description` or a runner can parse it.
```
kaizen-agentic schedule init [--target PATH] [--timezone TZ] [--force]
kaizen-agentic schedule init --engagement <slug> [--agents A,B] [--bootstrap-cadence hourly|daily|weekly]
kaizen-agentic schedule validate [--target PATH]
kaizen-agentic schedule list [--target PATH] [--all]
kaizen-agentic schedule prepare <agent> [--target PATH] [--format markdown|json]

View 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)

View File

@@ -35,9 +35,11 @@ action:
**Threshold:** 0.8 success rate, minimum 5 executions (avoids noise on early pilots).
**CLI mapping:** Event emitter is future work; manual check today:
**CLI mapping:** `kaizen-agentic metrics record <agent> --emit-event` after each
append (see [kaizen-metrics-recorded-event.md](../kaizen-metrics-recorded-event.md)).
Manual check today:
```bash
kaizen-agentic metrics show <agent> # inspect summary.success_rate
kaizen-agentic metrics optimize <agent>
```
```

View File

@@ -33,15 +33,15 @@ returns one `scheduled_run` per `(repo, agent)`; this definition selects the
id: run-weekly-coach
for_each: context.scheduled_runs
bind_as: r
condition: 'r.agent == "coach" and r.enabled == true'
condition: 'context.r.agent == "coach" and context.r.enabled'
action:
task_template: "Weekly coach orientation: {{r.repo}}"
task_template: "Weekly coach orientation: {context.r.repo}"
description: |
{{r.prepare_command}}
{context.r.prepare_command}
Then load agents/agent-coach.md in a coding-agent session, paste the
prepared bundle, and follow the coach synthesis. At session close:
kaizen-agentic metrics record coach --success --time <s> --quality <0-1>
target_repo: "{{r.repo}}"
target_repo: context.r.repo
priority: medium
labels: ["kaizen", "agent-run", "coach", "scheduled", "automated"]
```

View File

@@ -27,13 +27,13 @@ Invokes the kaizen-agentic optimizer CLI per project.
id: run-weekly-optimizer
for_each: context.projects
bind_as: p
condition: 'p.has_metrics == true'
condition: 'context.p.has_metrics'
action:
task_template: "Run kaizen metrics optimize on {{p.repo}}"
task_template: "Run kaizen metrics optimize on {context.p.repo}"
description: |
cd {{p.root}} && kaizen-agentic metrics optimize
cd {context.p.root} && kaizen-agentic metrics optimize
Optional: kaizen-agentic metrics publish (when artifact-store configured)
target_repo: "{{p.repo}}"
target_repo: context.p.repo
priority: medium
labels: ["kaizen", "metrics", "optimizer", "automated"]
```
@@ -41,4 +41,4 @@ action:
**Activation:** sync this definition into activity-core via `make sync-activity-definitions`
after enabling the shell resolver for `discover_kaizen_projects`.
**CLI mapping:** `kaizen-agentic metrics optimize` (no agent filter = all agents with metrics).
**CLI mapping:** `kaizen-agentic metrics optimize` (no agent filter = all agents with metrics).

View File

@@ -30,16 +30,16 @@ review is evidence-backed.
id: run-weekly-optimization
for_each: context.scheduled_runs
bind_as: r
condition: 'r.agent == "optimization" and r.enabled == true'
condition: 'context.r.agent == "optimization" and context.r.enabled'
action:
task_template: "Weekly optimization review: {{r.repo}}"
task_template: "Weekly optimization review: {context.r.repo}"
description: |
{{r.prepare_command}}
{context.r.prepare_command}
kaizen-agentic metrics optimize # refresh evidence
Then load agents/agent-optimization.md, paste the prepared bundle plus the
optimizer report, and act on recommendations. At session close:
kaizen-agentic metrics record optimization --success --time <s> --quality <0-1>
target_repo: "{{r.repo}}"
target_repo: context.r.repo
priority: medium
labels: ["kaizen", "agent-run", "optimization", "scheduled", "automated"]
```

View 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-*`

View File

@@ -1,6 +1,6 @@
# Resolver Spec: `discover_kaizen_scheduled_repos`
**Status:** specification — **implemented in activity-core**, not here. This doc
**Status:** implemented in activity-core (`context_resolvers/kaizen.py`). This doc
is the contract an activity-core implementer needs to add the context resolver
that feeds the scheduled-agent ActivityDefinitions (ADR-005).

View 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)

View File

@@ -70,11 +70,18 @@ the distribution) may appear in a schedule.
kaizen-agentic schedule init # defaults: coach + optimization weekly
kaizen-agentic schedule init --timezone UTC # override timezone
kaizen-agentic schedule init --force # overwrite existing
kaizen-agentic schedule init --engagement coulomb-loop \
--agents coach,optimization --bootstrap-cadence hourly
```
The default scaffold enables `coach` and `optimization` weekly and declares
`tdd-workflow` monthly but **disabled** (operator opts in deliberately).
**Engagement bootstrap** (`--engagement`) writes customer-loop presets: hourly
bootstrap uses `cadence: daily` with hourly `cron` overrides (coach `:15`,
optimization `:30`). See
[customer-engagement-playbook.md](customer-engagement-playbook.md).
## Listing
```bash

View File

@@ -32,6 +32,9 @@ dependencies = [
]
[project.optional-dependencies]
events = [
"nats-py>=2.6.0",
]
dev = [
"pytest>=6.0.0",
"pytest-cov>=4.0.0",

View File

@@ -15,12 +15,18 @@ from .integrations.artifact_store import (
default_api_url,
publish_optimizer_evidence,
)
from .integrations.event_bus import (
build_metrics_recorded_envelope,
publish_metrics_recorded_event,
resolve_project_slug,
)
from .integrations.helix import HelixCorrelationAdapter, enrich_helix_correlation
from .metrics import MetricsStore, OptimizerStore, performance_summary_markdown
from .optimization import OptimizationLoop, MIN_SAMPLES_FOR_RECOMMENDATIONS
from .schedule import (
ScheduleError,
default_schedule_yaml,
engagement_schedule_yaml,
load_schedule,
schedule_path,
validate_schedule,
@@ -1069,6 +1075,11 @@ def metrics():
@click.option(
"--json", "json_input", is_flag=True, help="Read full record JSON from stdin"
)
@click.option(
"--emit-event",
is_flag=True,
help="Publish kaizen.metrics.recorded to NATS (requires nats-py)",
)
def metrics_record(
agent_name: str,
target: str,
@@ -1079,6 +1090,7 @@ def metrics_record(
session_id: Optional[str],
idempotency_key: Optional[str],
json_input: bool,
emit_event: bool,
):
"""Append one execution record for an agent."""
store = MetricsStore(_project_root(target), agent_name)
@@ -1109,6 +1121,21 @@ def metrics_record(
if store.append(payload, idempotency_key=idempotency_key):
click.echo(f"Recorded metrics for '{agent_name}'")
if emit_event:
summary = store.read_summary() or store.write_summary()
envelope = build_metrics_recorded_envelope(
agent=agent_name,
project=resolve_project_slug(store.project_root),
summary=summary,
)
try:
subject = publish_metrics_recorded_event(envelope)
except RuntimeError as exc:
click.echo(f"Error: {exc}", err=True)
sys.exit(1)
click.echo(
f"Emitted kaizen.metrics.recorded for '{agent_name}'{subject}"
)
else:
click.echo(
f"Skipped duplicate record for '{agent_name}' (idempotency key exists)"
@@ -1429,8 +1456,39 @@ def schedule_validate(target: str):
"--timezone", default="Europe/Berlin", show_default=True, help="Schedule timezone"
)
@click.option("--force", is_flag=True, help="Overwrite an existing schedule.yml")
def schedule_init(target: str, timezone: str, force: bool):
"""Scaffold a default .kaizen/schedule.yml (coach + optimization weekly)."""
@click.option(
"--engagement",
default=None,
help="Customer engagement slug (bootstrap schedule for target repos)",
)
@click.option(
"--agents",
default=None,
help="Comma-separated agents for --engagement (default: coach,optimization)",
)
@click.option(
"--bootstrap-cadence",
type=click.Choice(["hourly", "daily", "weekly"]),
default="hourly",
show_default=True,
help="Cadence preset for --engagement (hourly uses daily enum + hourly cron)",
)
def schedule_init(
target: str,
timezone: str,
force: bool,
engagement: Optional[str],
agents: Optional[str],
bootstrap_cadence: str,
):
"""Scaffold .kaizen/schedule.yml (weekly default or engagement bootstrap)."""
if (agents or bootstrap_cadence != "hourly") and not engagement:
click.echo(
"Error: --agents and --bootstrap-cadence require --engagement",
err=True,
)
sys.exit(1)
path = schedule_path(_project_root(target))
if path.exists() and not force:
@@ -1438,9 +1496,41 @@ def schedule_init(target: str, timezone: str, force: bool):
click.echo(" Use --force to overwrite.")
return
if engagement:
agent_list = (
[item.strip() for item in agents.split(",") if item.strip()]
if agents
else None
)
known_agents = _get_registry().agent_names()
if agent_list:
unknown = [name for name in agent_list if name not in known_agents]
if unknown:
click.echo(
f"Error: unknown agent(s) for engagement schedule: {', '.join(unknown)}",
err=True,
)
sys.exit(1)
try:
yaml_text = engagement_schedule_yaml(
engagement,
agents=agent_list,
bootstrap_cadence=bootstrap_cadence,
timezone=timezone,
)
except ScheduleError as exc:
click.echo(f"Error: {exc}", err=True)
sys.exit(1)
else:
yaml_text = default_schedule_yaml(timezone=timezone)
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(default_schedule_yaml(timezone=timezone), encoding="utf-8")
path.write_text(yaml_text, encoding="utf-8")
click.echo(f"Initialized schedule: {path}")
if engagement:
click.echo(
f" Engagement: {engagement} (bootstrap-cadence={bootstrap_cadence})"
)
click.echo(" Validate with: kaizen-agentic schedule validate")

View File

@@ -1,10 +1,18 @@
"""Ecosystem integration adapters (Helix Forge, artifact-store)."""
"""Ecosystem integration adapters (Helix Forge, artifact-store, event bus)."""
from .artifact_store import publish_optimizer_evidence
from .event_bus import (
build_metrics_recorded_envelope,
publish_metrics_recorded_event,
resolve_project_slug,
)
from .helix import HelixCorrelationAdapter, enrich_helix_correlation
__all__ = [
"HelixCorrelationAdapter",
"build_metrics_recorded_envelope",
"enrich_helix_correlation",
"publish_metrics_recorded_event",
"publish_optimizer_evidence",
"resolve_project_slug",
]

View 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

View File

@@ -27,6 +27,29 @@ DEFAULT_AGENTS: Dict[str, Dict[str, Any]] = {
"tdd-workflow": {"cadence": "monthly", "enabled": False},
}
DEFAULT_TIMEZONE = "Europe/Berlin"
DEFAULT_ENGAGEMENT_AGENTS = ("coach", "optimization")
# Bootstrap cadence presets for customer engagements (coulomb-loop ADR-003).
# Hourly bootstrap keeps cadence enum ``daily`` so activity-core definitions
# filtering ``cadence: daily`` still match while per-repo cron overrides fire
# hourly (see docs/integrations/activity-core-handoff-engagement.md).
ENGAGEMENT_CADENCE_PRESETS: Dict[str, Dict[str, Dict[str, Any]]] = {
"hourly": {
"coach": {"cadence": "daily", "cron": "15 * * * *", "enabled": True},
"optimization": {"cadence": "daily", "cron": "30 * * * *", "enabled": True},
"tdd-workflow": {"cadence": "monthly", "enabled": False},
},
"daily": {
"coach": {"cadence": "daily", "cron": "0 8 * * *", "enabled": True},
"optimization": {"cadence": "daily", "cron": "0 9 * * *", "enabled": True},
"tdd-workflow": {"cadence": "monthly", "enabled": False},
},
"weekly": {
"coach": {"cadence": "weekly", "cron": "0 9 * * 1", "enabled": True},
"optimization": {"cadence": "weekly", "cron": "0 10 * * 1", "enabled": True},
"tdd-workflow": {"cadence": "monthly", "enabled": False},
},
}
class ScheduleError(Exception):
@@ -183,3 +206,56 @@ def default_schedule_yaml(timezone: str = DEFAULT_TIMEZONE) -> str:
)
body = yaml.safe_dump(document, sort_keys=False, default_flow_style=False)
return header + body
def engagement_schedule_yaml(
engagement: str,
*,
agents: Optional[List[str]] = None,
bootstrap_cadence: str = "hourly",
timezone: str = DEFAULT_TIMEZONE,
) -> str:
"""Render a customer-engagement bootstrap schedule for `schedule init --engagement`."""
if bootstrap_cadence not in ENGAGEMENT_CADENCE_PRESETS:
raise ScheduleError(
f"unsupported bootstrap cadence '{bootstrap_cadence}' "
f"(expected one of {', '.join(ENGAGEMENT_CADENCE_PRESETS)})"
)
slug = engagement.strip()
if not slug:
raise ScheduleError("engagement slug must not be empty")
selected = list(agents or DEFAULT_ENGAGEMENT_AGENTS)
if not selected:
raise ScheduleError(
"at least one agent is required for engagement schedule init"
)
preset = ENGAGEMENT_CADENCE_PRESETS[bootstrap_cadence]
agent_entries: Dict[str, Dict[str, Any]] = {}
for name in selected:
if name not in preset:
raise ScheduleError(
f"agent '{name}' has no preset for bootstrap cadence '{bootstrap_cadence}'"
)
agent_entries[name] = dict(preset[name])
if bootstrap_cadence == "hourly":
cadence_note = "hourly crons, daily cadence enum"
else:
cadence_note = f"{bootstrap_cadence} cadence"
document = {
"version": SCHEDULE_VERSION,
"timezone": timezone,
"agents": agent_entries,
}
header = (
"# Kaizen scheduled agent execution manifest (ADR-005)\n"
f"# Engagement: {slug} bootstrap — {cadence_note}\n"
"# Regulator promotes cadence per customer engagement policy (ADR-003).\n"
"# Validate with: kaizen-agentic schedule validate\n"
)
body = yaml.safe_dump(document, sort_keys=False, default_flow_style=False)
return header + body

View 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

View File

@@ -6,11 +6,13 @@ import json
from pathlib import Path
import pytest
import yaml
from click.testing import CliRunner
from kaizen_agentic.cli import cli
from kaizen_agentic.schedule import (
ScheduleError,
engagement_schedule_yaml,
parse_schedule,
schedule_path,
validate_schedule,
@@ -30,6 +32,20 @@ def project_dir(tmp_path: Path) -> Path:
class TestScheduleModule:
def test_engagement_schedule_yaml_hourly_preset(self):
text = engagement_schedule_yaml(
"coulomb-loop",
agents=["coach", "optimization"],
bootstrap_cadence="hourly",
)
assert "Engagement: coulomb-loop bootstrap" in text
body = "\n".join(line for line in text.splitlines() if not line.startswith("#"))
schedule = parse_schedule(yaml.safe_load(body))
coach = schedule.entry_for("coach")
assert coach is not None
assert coach.cadence == "daily"
assert coach.cron == "15 * * * *"
def test_parse_requires_version(self):
with pytest.raises(ScheduleError):
parse_schedule({"agents": {}})
@@ -67,6 +83,67 @@ class TestScheduleCli:
assert path.exists()
assert "coach" in path.read_text()
def test_engagement_init_hourly_bootstrap(
self, runner: CliRunner, project_dir: Path
):
result = runner.invoke(
cli,
[
"schedule",
"init",
"--target",
str(project_dir),
"--engagement",
"coulomb-loop",
"--agents",
"coach,optimization",
"--bootstrap-cadence",
"hourly",
],
)
assert result.exit_code == 0, result.output
text = schedule_path(project_dir).read_text()
assert "Engagement: coulomb-loop bootstrap" in text
assert "cron: 15 * * * *" in text
assert "cadence: daily" in text
assert "Engagement: coulomb-loop" in result.output
def test_engagement_init_validates_unknown_agent(
self, runner: CliRunner, project_dir: Path
):
result = runner.invoke(
cli,
[
"schedule",
"init",
"--target",
str(project_dir),
"--engagement",
"demo",
"--agents",
"not-a-real-agent",
],
)
assert result.exit_code == 1
assert "unknown agent" in result.output
def test_engagement_flags_require_engagement_slug(
self, runner: CliRunner, project_dir: Path
):
result = runner.invoke(
cli,
[
"schedule",
"init",
"--target",
str(project_dir),
"--agents",
"coach",
],
)
assert result.exit_code == 1
assert "--engagement" in result.output
def test_init_no_overwrite_without_force(
self, runner: CliRunner, project_dir: Path
):

View File

@@ -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-00010004.
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 (T01T02, T05) ── parallel with coulomb-loop smoke test
Part 2 (T03T04, T06T09) ── after first hourly E2E pass
Part 3 (T07T08, 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 |