feat: schedule init --engagement for customer bootstrap presets
Some checks failed
ci / test (push) Has been cancelled
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.
This commit is contained in:
@@ -26,6 +26,7 @@ from .optimization import OptimizationLoop, MIN_SAMPLES_FOR_RECOMMENDATIONS
|
||||
from .schedule import (
|
||||
ScheduleError,
|
||||
default_schedule_yaml,
|
||||
engagement_schedule_yaml,
|
||||
load_schedule,
|
||||
schedule_path,
|
||||
validate_schedule,
|
||||
@@ -1455,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:
|
||||
@@ -1464,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")
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user