agent-compose is not idempotent: rewrites COMPOSED.md + re-links and reports changed every run #177

Closed
opened 2026-06-05 20:23:55 +00:00 by coilysiren · 2 comments
Owner

Problem

agentic_os.agent_compose is not idempotent: every run rewrites COMPOSED.md and re-creates the harness symlinks even when nothing has changed, and it prints the wrote / linked markers unconditionally. Any downstream consumer that keys off those markers (the ansible agent-compose role's changed_when, the freshness sweep's check-mode preview) therefore sees the host as perpetually drifted.

Observed: a clean ansible-freshen action=apply tags=agent-compose followed by an immediate idempotent re-check still reports changed=1 on the compose task, forever.

Root cause (agent_compose.py)

  • run() (around line 291): composed_path.write_text(body) then print("wrote ...") run unconditionally - no comparison against the file already on disk.
  • install_symlink() (around line 203): always unlink() + symlink_to() and returns the linked line, even when dst is already a symlink pointing at the correct target.

Fix - in agent-compose, not downstream

agent-compose should be the one that doesn't touch the file when it's unchanged. Pushing a changed_when heuristic into the ansible role (or anywhere downstream) just papers over non-idempotent behavior at the source.

  • Write: compare body to composed_path.read_text() when the file exists; only write when different. Print unchanged <path> (or stay quiet) on a no-op so callers can distinguish.
  • Symlink: if dst is already a symlink resolving to the intended target, leave it and report ok / unchanged instead of linked .
  • Keep --dry-run output meaningful: would write only when it actually would, would link only for links that are missing or wrong.

Net goal: a converged host runs python3 -m agentic_os.agent_compose (and ansible-freshen tags=agent-compose) as a true no-op - no file writes, no changed.

Downstream note

The ansible role's changed_when (keys on linked /wrote /would in stdout) is correct once the markers only fire on real changes - no role change needed beyond that. Filed first (mis-scoped) as coilyco-flight-deck/infrastructure#230, now closed in favor of this.

Found during a freshness sweep on the Kapwing host, 2026-06-05.

## Problem `agentic_os.agent_compose` is not idempotent: every run rewrites `COMPOSED.md` and re-creates the harness symlinks even when nothing has changed, and it prints the `wrote ` / `linked ` markers unconditionally. Any downstream consumer that keys off those markers (the ansible `agent-compose` role's `changed_when`, the freshness sweep's check-mode preview) therefore sees the host as perpetually drifted. Observed: a clean `ansible-freshen action=apply tags=agent-compose` followed by an immediate idempotent re-check still reports `changed=1` on the compose task, forever. ## Root cause (`agent_compose.py`) - `run()` (around line 291): `composed_path.write_text(body)` then `print("wrote ...")` run unconditionally - no comparison against the file already on disk. - `install_symlink()` (around line 203): always `unlink()` + `symlink_to()` and returns the `linked ` line, even when `dst` is already a symlink pointing at the correct target. ## Fix - in agent-compose, not downstream agent-compose should be the one that doesn't touch the file when it's unchanged. Pushing a `changed_when` heuristic into the ansible role (or anywhere downstream) just papers over non-idempotent behavior at the source. - **Write:** compare `body` to `composed_path.read_text()` when the file exists; only write when different. Print `unchanged <path>` (or stay quiet) on a no-op so callers can distinguish. - **Symlink:** if `dst` is already a symlink resolving to the intended target, leave it and report `ok` / `unchanged` instead of `linked `. - Keep `--dry-run` output meaningful: `would write` only when it actually would, `would link` only for links that are missing or wrong. Net goal: a converged host runs `python3 -m agentic_os.agent_compose` (and `ansible-freshen tags=agent-compose`) as a true no-op - no file writes, no `changed`. ## Downstream note The ansible role's `changed_when` (keys on `linked `/`wrote `/`would ` in stdout) is correct once the markers only fire on real changes - no role change needed beyond that. Filed first (mis-scoped) as coilyco-flight-deck/infrastructure#230, now closed in favor of this. Found during a freshness sweep on the Kapwing host, 2026-06-05.
Author
Owner

Goose triage - 2026-06-17

  • Tier: P2 (score 85) - Critical idempotency issue impacting automation
  • Mode: headless - Clear fix specification, no human decision needed

Auto-generated by ward exec goose-triage (qwen3-coder:30b), edited in place on each run. Labels are the conclusion; this is the why.

<!-- goose-triage --> **Goose triage** - 2026-06-17 - **Tier:** `P2` (score 85) - Critical idempotency issue impacting automation - **Mode:** `headless` - Clear fix specification, no human decision needed <sub>Auto-generated by `ward exec goose-triage` (qwen3-coder:30b), edited in place on each run. Labels are the conclusion; this is the why.</sub>
Member

🔒 Reserved by ward agent claude — container ward-agentic-os-issue-177-claude-65247c93 on host kais-macbook-pro.local is carrying this issue (reserved 2026-06-18T08:13:05Z). Concurrent ward agent runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); --force overrides.

— Claude (she/her), via ward agent

<!-- ward-agent-reservation --> 🔒 Reserved by `ward agent claude` — container `ward-agentic-os-issue-177-claude-65247c93` on host `kais-macbook-pro.local` is carrying this issue (reserved 2026-06-18T08:13:05Z). Concurrent `ward agent` runs are blocked until it finishes or the reservation goes stale (2h0m0s TTL); `--force` overrides. <!-- ward-agent-signature --> — Claude (she/her), via `ward agent`
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agentic-os#177
No description provided.