Review fixes for the markdown loop feature:
- Loop-owned tasks now adopt by loop file path, not task name, so renaming
a loop (frontmatter name or UI rename) renames the task in place instead
of leaving a stale duplicate that keeps running the old definition;
orphan duplicates of the same file are unscheduled.
- Unparseable loop files are reported to the scheduler as
definition:null entries: a task whose file still exists is kept with its
last good definition, and only a genuinely removed file unschedules it.
Transiently malformed files (mid-edit, bad merge) no longer delete tasks
or their runtime state.
- Adoption preserves UI-only execution fields (goalEnabled, goalTokenBudget,
permissionAutoAccept, variant) that the portable format does not define.
- DELETE on a loop-sourced task now returns 400 with guidance to remove the
loop file, instead of being silently undone by the next reconcile.
- Loops default to enabled: false; discovery of repository content never
auto-executes scheduled sessions unless the file explicitly enables them.
Regression tests for each fix; DOCUMENTATION.md updated.
Adds markdown-based scheduled-task definitions ("loops") discovered from
.agents/loops/*.md (project scope, ancestor directories up to the
worktree root) and ~/.agents/loops/*.md (user scope), mirroring the
skills discovery pattern.
File format: YAML frontmatter (name, schedule cron, enabled, model as
provider/model, optional agent/timezone) plus the markdown body as the
execution prompt. Discovery and parsing live in
scheduled-tasks/loops.js; project-config gains reconcileLoopTasks which
runs inside the project write lock on every syncProject:
- identity by task name; a loop takes over a matching task, preserving
its id and runtime state (markdown wins on conflict with JSON)
- tasks whose loopFile is gone are unscheduled; JSON tasks are never
removed
- new loops are created under deterministic loop:<scope>:<name> ids
- project scope shadows user scope on name collisions
- malformed files are skipped with a warning and never block valid ones
Runtime state stays in the project config/state store; it is never
written to the markdown files. Module documentation updated with the
file format and reconciliation rules.
Fixes#2583