flower
/
All briefs
complete draft note flower

Hook-driven worktree environment reconciliation at dispatch/spawn (composer/npm/.env drift — off the agent's plate)

canonical · plan

Spec

markdown

hand-off · dispatch

Dispatch

Auto-dispatch

when it reaches planned

Design-loop

design pass before build

This brief is complete — dispatch is closed.

#72 done fresh flower · flower/184-worktree-prep
agent: claude 1 scratchpad
You are being dispatched from flower Brief #184: Hook-driven worktree environment reconciliation at dispatch/spawn (composer/npm/.env drift — off the agent's plate)

Recall pointer:
- Use recall_brief with id 184 for the full folder if you need provenance.

Target:
- project: flower (/Users/mikeferrara/Documents/code/flower)
- branch: flower/184-worktree-prep
- worktree: not specified
- kind: fresh

Current brief spec:
## DECIDED (operator:mike, 2026-07-04 — authoritative; supersedes the earlier "leans")
All three round-1 questions answered:
1. **Home:** ONE shared idempotent `bin/prepare-worktree.sh` (superset of `setup-worktree-db.sh`), called by BOTH the app spawn-path AND worktree-manager's post-create hook — single source of truth.
2. **Prep scope:** `composer install` + `.env` key-backfill + DB/Redis isolation + `npm ci` **+ `npm run build`** — operator chose the FULL asset build in every worktree (accepts the per-dispatch build cost; do NOT skip build).
3. **On prep failure:** **block the spawn + surface to the orchestrator** — never spawn a doomed worker.

## Goal
When a task is dispatched to an agent in a worktree, a server-side pre-spawn step (fired from the dispatch/spawn path, NOT carried by the agent or daemon) solidifies the worktree environment BEFORE the worker's first turn. Fast no-op when nothing drifted.

## Problem — three drift classes, one root cause
worktree-manager copies MAIN's `.env` at create time and `bin/setup-worktree-db.sh` isolates DB+Redis, but everything else drifts AFTER creation as MAIN's lockfiles/config move:
- **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), **#120 (this session, foundation — had to composer-install mid-task)**, sessions 860/863, #91's worker (3293).
- **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d/built.
- **`.env` key drift** — MAIN gains keys not backfilled into the worktree copy (CLAUDE.md env-drift gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → silent worktree-vs-MAIN divergence (the worst class because it's silent).

## Build direction
1. **Idempotent `bin/prepare-worktree.sh`** (superset of `setup-worktree-db.sh`). For a given worktree: (a) ensure branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` + `npm run build` **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare → **fast no-op when nothing drifted** → safe to run on every dispatch.
2. **Server-side pre-spawn gate.** Insert a "prepare worktree → then spawn" step in the dispatch path — the `DispatchService` `$spawn` seam (`Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22) — synchronous + idempotent, so the env is guaranteed ready before the worker's first turn. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker.
3. **One source of truth.** The generic reconcile logic lives in the shell script; both the app spawn-path and worktree-manager's post-create hook call the same thing.

## Reuse / substrate (do NOT rebuild)
- `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate.
- `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing.
- worktree-manager (shared Go CLI) post-create hook slot.

## Out of scope
- Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve).
- The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here.

## Acceptance
- A dispatch into a drifted worktree reconciles (composer + `.env` + `npm ci`/`npm run build`) BEFORE the worker starts; a fresh/clean worktree is a fast no-op (stamp hit). Prep outcome recorded on the `dispatch_request`; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected→runs, no-drift→skips, failure→blocks. `php artisan test` green + `pint`. `Brief: #184` trailer.

## Provenance
Operator note 2026-07-04 (post-#119) → orchestrator grounding; operator answered the 3 design questions 2026-07-04 03:19–03:20. `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119/#120. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.

Recent/key trace events:
[3] plan_proposed flower-orchestrator: ## Idea (operator, 2026-07-04)
Prompted by the #119 vendor-drift flag. When a task is pushed to an agent in a worktree, an automated step — fired from an MCP action / hook, **not** carried by the agent or daemon — should solidify the worktree environment (composer install, etc.) BEFORE the worker's first turn.

## Problem — three drift classes, one root cause
worktree-manager copies MAIN's `.env` at create time, and `bin/setup-worktree-db.sh` (already "intended as a worktree-manager post-create hook") isolates DB + Redis. But everything else drifts AFTER creation because MAIN's lockfiles/config move and the worktree snapshot doesn't:
- **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), plus sessions 860, 863, and #91's worker (3293) all hit "vendor missing / FluxUI not installed".
- **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d.
- **`.env` key drift** — MAIN gains env keys not backfilled into the worktree copy (CLAUDE.md "env drift" gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → env-sensitive config/tests pass in the worktree but fail on MAIN. Arguably the *worst* class because it's silent.

Today the mitigation is tribal: each worker/orchestrator must remember to check and reconcile. That is exactly the responsibility the operator wants moved into the environment.

## Direction (proposed)
1. **Idempotent `bin/prepare-worktree.sh`** — a superset of `setup-worktree-db.sh`. For a given worktree it: (a) ensures branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` (build TBD, see Q2) **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare -> **fast no-op when nothing drifted**, so it is safe to run on every dispatch.
2. **Wire it as a server-side pre-spawn gate in the dispatch path.** flower's spawn is orchestrator-mediated through `DispatchService` (the `$spawn` closure: `Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22). Insert a "prepare worktree -> then spawn" step there — synchronous + idempotent — so the env is guaranteed ready before the worker's first turn. This is the MCP-action-as-hook the operator asked for: it fires from `brief_dispatch`/spawn, not from the agent. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker (see Q3).
3. **One source of truth.** Keep the generic reconcile logic in the shell script so BOTH the app spawn-path AND worktree-manager's post-create hook call the same thing (see Q1).

## Recommendations (my leans, up for debate in the questions)
- Trigger **just before spawn** (freshest; "before they start"), synchronous + idempotent-fast — not create-time only (drift happens later) and not async.
- Prep scope: **composer install + `.env` key-backfill + DB/Redis** by default; **npm ci without build** (workers sqlite-test and never serve — build/serve stays MAIN's job at merge time, which already works). Revisit if a worker actually needs built assets.
- Failure = **block + surface**, never spawn into a known-broken env.

## Reuse / substrate (do NOT rebuild)
- `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate.
- `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing.
- worktree-manager (shared Go CLI) post-create hook slot.

## Out of scope
- Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve).
- The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here.

## Acceptance (sketch, pending answers)
- A dispatch into a drifted worktree reconciles (composer/.env at minimum) BEFORE the worker starts; a fresh worktree is a fast no-op (stamp hit). Prep outcome recorded on the dispatch_request; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected->runs, no-drift->skips, failure->blocks. `php artisan test` green + pint. `Brief: #184` trailer.

## Provenance
Operator note 2026-07-04 (post-#119) -> orchestrator grounding (this session): `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.
[4] agent_question flower-orchestrator: Given you want it hook-triggered, where should the reconcile logic live?
[5] agent_question flower-orchestrator: What should the pre-spawn prep do by default?
[6] agent_question flower-orchestrator: If prep fails (e.g. composer install errors), what should happen?
[7] participant_joined operator:mike: (no body)
[8] operator_answer operator:mike: {"choice":"Block the spawn + surface to the orchestrator — never spawn a doomed worker (recommended)"}
[9] operator_answer operator:mike: {"choice":"Add npm run build too (full asset build in every worktree)"}
[10] operator_answer operator:mike: {"choice":"Shared bin/prepare-worktree.sh called by BOTH the app spawn-path and worktree-manager (one source of truth) — recommended"}
[11] spec_snapshot flower-orchestrator: ## Idea (operator, 2026-07-04)
Prompted by the #119 vendor-drift flag. When a task is pushed to an agent in a worktree, an automated step — fired from an MCP action / hook, **not** carried by the agent or daemon — should solidify the worktree environment (composer install, etc.) BEFORE the worker's first turn.

## Problem — three drift classes, one root cause
worktree-manager copies MAIN's `.env` at create time, and `bin/setup-worktree-db.sh` (already "intended as a worktree-manager post-create hook") isolates DB + Redis. But everything else drifts AFTER creation because MAIN's lockfiles/config move and the worktree snapshot doesn't:
- **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), plus sessions 860, 863, and #91's worker (3293) all hit "vendor missing / FluxUI not installed".
- **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d.
- **`.env` key drift** — MAIN gains env keys not backfilled into the worktree copy (CLAUDE.md "env drift" gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → env-sensitive config/tests pass in the worktree but fail on MAIN. Arguably the *worst* class because it's silent.

Today the mitigation is tribal: each worker/orchestrator must remember to check and reconcile. That is exactly the responsibility the operator wants moved into the environment.

## Direction (proposed)
1. **Idempotent `bin/prepare-worktree.sh`** — a superset of `setup-worktree-db.sh`. For a given worktree it: (a) ensures branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` (build TBD, see Q2) **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare -> **fast no-op when nothing drifted**, so it is safe to run on every dispatch.
2. **Wire it as a server-side pre-spawn gate in the dispatch path.** flower's spawn is orchestrator-mediated through `DispatchService` (the `$spawn` closure: `Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22). Insert a "prepare worktree -> then spawn" step there — synchronous + idempotent — so the env is guaranteed ready before the worker's first turn. This is the MCP-action-as-hook the operator asked for: it fires from `brief_dispatch`/spawn, not from the agent. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker (see Q3).
3. **One source of truth.** Keep the generic reconcile logic in the shell script so BOTH the app spawn-path AND worktree-manager's post-create hook call the same thing (see Q1).

## Recommendations (my leans, up for debate in the questions)
- Trigger **just before spawn** (freshest; "before they start"), synchronous + idempotent-fast — not create-time only (drift happens later) and not async.
- Prep scope: **composer install + `.env` key-backfill + DB/Redis** by default; **npm ci without build** (workers sqlite-test and never serve — build/serve stays MAIN's job at merge time, which already works). Revisit if a worker actually needs built assets.
- Failure = **block + surface**, never spawn into a known-broken env.

## Reuse / substrate (do NOT rebuild)
- `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate.
- `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing.
- worktree-manager (shared Go CLI) post-create hook slot.

## Out of scope
- Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve).
- The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here.

## Acceptance (sketch, pending answers)
- A dispatch into a drifted worktree reconciles (composer/.env at minimum) BEFORE the worker starts; a fresh worktree is a fast no-op (stamp hit). Prep outcome recorded on the dispatch_request; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected->runs, no-drift->skips, failure->blocks. `php artisan test` green + pint. `Brief: #184` trailer.

## Provenance
Operator note 2026-07-04 (post-#119) -> orchestrator grounding (this session): `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.
[12] refinement flower-orchestrator: ## DECIDED (operator:mike, 2026-07-04 — authoritative; supersedes the earlier "leans")
All three round-1 questions answered:
1. **Home:** ONE shared idempotent `bin/prepare-worktree.sh` (superset of `setup-worktree-db.sh`), called by BOTH the app spawn-path AND worktree-manager's post-create hook — single source of truth.
2. **Prep scope:** `composer install` + `.env` key-backfill + DB/Redis isolation + `npm ci` **+ `npm run build`** — operator chose the FULL asset build in every worktree (accepts the per-dispatch build cost; do NOT skip build).
3. **On prep failure:** **block the spawn + surface to the orchestrator** — never spawn a doomed worker.

## Goal
When a task is dispatched to an agent in a worktree, a server-side pre-spawn step (fired from the dispatch/spawn path, NOT carried by the agent or daemon) solidifies the worktree environment BEFORE the worker's first turn. Fast no-op when nothing drifted.

## Problem — three drift classes, one root cause
worktree-manager copies MAIN's `.env` at create time and `bin/setup-worktree-db.sh` isolates DB+Redis, but everything else drifts AFTER creation as MAIN's lockfiles/config move:
- **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), **#120 (this session, foundation — had to composer-install mid-task)**, sessions 860/863, #91's worker (3293).
- **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d/built.
- **`.env` key drift** — MAIN gains keys not backfilled into the worktree copy (CLAUDE.md env-drift gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → silent worktree-vs-MAIN divergence (the worst class because it's silent).

## Build direction
1. **Idempotent `bin/prepare-worktree.sh`** (superset of `setup-worktree-db.sh`). For a given worktree: (a) ensure branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` + `npm run build` **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare → **fast no-op when nothing drifted** → safe to run on every dispatch.
2. **Server-side pre-spawn gate.** Insert a "prepare worktree → then spawn" step in the dispatch path — the `DispatchService` `$spawn` seam (`Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22) — synchronous + idempotent, so the env is guaranteed ready before the worker's first turn. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker.
3. **One source of truth.** The generic reconcile logic lives in the shell script; both the app spawn-path and worktree-manager's post-create hook call the same thing.

## Reuse / substrate (do NOT rebuild)
- `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate.
- `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing.
- worktree-manager (shared Go CLI) post-create hook slot.

## Out of scope
- Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve).
- The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here.

## Acceptance
- A dispatch into a drifted worktree reconciles (composer + `.env` + `npm ci`/`npm run build`) BEFORE the worker starts; a fresh/clean worktree is a fast no-op (stamp hit). Prep outcome recorded on the `dispatch_request`; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected→runs, no-drift→skips, failure→blocks. `php artisan test` green + `pint`. `Brief: #184` trailer.

## Provenance
Operator note 2026-07-04 (post-#119) → orchestrator grounding; operator answered the 3 design questions 2026-07-04 03:19–03:20. `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119/#120. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.
[13] status_change flower-orchestrator: (no body)
[14] link_added flower-orchestrator: (no body)

Recommended linked context:
{
    "todos": [],
    "scratchpads": [
        {
            "id": 381,
            "solo_scratchpad_id": "1073",
            "name": "flower-orchestrator — reset handoff (2026-07-04)",
            "archived": false,
            "revision": 1
        }
    ]
}

Execution notes:
- Treat the brief as the source of truth.
- Keep work scoped to this dispatch request.
- Use brief_append / brief_update_status when reporting material progress; as your final dispatched-worker step, call brief_dispatch_complete with dispatch_request_id (or brief_id) and actor_ref.
- Codex workers should verify mutating Flower tools with tool_search query `brief_append brief_dispatch_complete flower_feedback` (limit 20) when tool availability is in doubt; report raw SEE/LOAD vs NOT visible instead of silently using local fallbacks.
- Add a git commit trailer `Brief: #184` to every commit for this brief so flower can exact-link commits back to the brief.

provenance · append-only

Trace

live
or paste a screenshot uploading…
  1. link added 1d ago
    agent · system:commit-trailer
  2. participant joined 1d ago
    system · system:commit-trailer
  3. merged 1d ago

    Merged flower/184-worktree-prep → master on MAIN (--no-ff), commit e4df9e1. Migration add_prep_outcome_to_dispatch_requests ran clean (80ms, no 64-char index issue). Full suite 898 pass / 1 skip / 0 fail, pint clean. Shipped: bin/prepare-worktree.sh (idempotent, stamp-gated, refuses MAIN) + WorktreePreparer + a pre-spawn gate at the DispatchService.$spawn seam (blocks spawn + surfaces to orchestrator inbox on failure; stamps prep_ok/prepared_at/prep_ran/prep_duration_ms/prep_detail on dispatch_request). Kill-switch flower.worktree.prepare_enabled. Scoped to worktree-targeted dispatches (MAIN/daemon spawns ungated). Directly addresses the worktree drift that bit #119/#120 (and the #123 leak recovery this session).

    agent · flower-orchestrator
  4. status change 1d ago
    agent · flower-184
  5. dispatched 1d ago

    Dispatch request #72 marked done.

    agent · flower-184
  6. note added 1d ago

    Built + committed on flower/184-worktree-prep (e4df9e1, trailer `Brief: #184`). All three DECIDED choices honored. **1. `bin/prepare-worktree.sh`** — idempotent superset of `setup-worktree-db.sh`: - (composer) `composer install` iff `composer.lock` hash != stamp or `vendor/` missing - (npm) `npm ci` + `npm run build` iff `package-lock.json` hash changed or `public/build` missing (full asset build per the operator's answer) - (env) backfill MAIN `.env` keys absent in the worktree, gated on MAIN's `.env` hash; NEVER clobbers `DB_DATABASE`/`REDIS_PREFIX` - (db) delegates to the existing `setup-worktree-db.sh` (not duplicated), gated so it only re-isolates/migrates when not-yet-isolated or composer changed - Stamps live in the worktree's PRIVATE git metadata dir (`.git/worktrees/<name>/flower-prep`) — never in the working tree, so `git status` stays clean and each worktree is auto-isolated. Emits a single JSON outcome on stdout (human log → stderr); refuses MAIN. Skip knobs + COMPOSER_BIN/NPM_BIN overrides. Smoke-tested live against this worktree: detected + backfilled a real drifted key (`REVERB_SERVER_PORT`), stamp-hit on rerun, refused MAIN. **2. Server-side pre-spawn gate** — wired at the `DispatchService.$spawn` seam via `DirectDispatchService` (the #22 spawn plumbing / `SoloClient::spawnAgent`). New `WorktreePreparer` (Process wrapper → structured outcome; a prep failure is `ok=false`, never an exception). On failure: BLOCKS the spawn (no dispatch_request, no doomed worker) + surfaces to the orchestrator via the operator inbox. On success: stamps `prep_ok / prepared_at / prep_ran / prep_duration_ms / prep_detail` onto the `dispatch_request` (new columns + migration `2026_07_04_000000`). Only worktree scopes are prepped (MAIN is the served checkout; the script refuses it). Kill-switch `flower.worktree.prepare_enabled` (forced off in phpunit so the suite never shells out). **Tests:** drift→runs+records, failure→blocks+surfaces, non-worktree→skips, plus `WorktreePreparer` JSON-parse / failure-synthesis / mode-passthrough. `MEILISEARCH_KEY=LARAVEL-HERD ~/bin/php artisan test` → 889 passed / 2 pre-existing skips / 0 failed. `pint` clean on all changed files. **Follow-up (out of scope here):** the "one source of truth" also wants worktree-manager's post-create hook to call `bin/prepare-worktree.sh` — that's a change in the external Go CLI repo (`legitphp/worktree-manager`), not this backend repo, so it's left for a separate task. The queued (non-live) dispatch path isn't gated (no synchronous spawn there); if desired, the orchestrator could run prep at claim time in a later brief. Left the noted perpetually-dirty `M bin/setup-worktree-db.sh` untouched per spec. Idle for merge — MAIN's Horizon won't pick up the gate until merged + (since it's not a queued job, no horizon reload needed; the served app picks up new code on next request).

    agent · flower-184
  7. participant joined 1d ago
    system · flower-184
  8. dispatched 1d ago

    Dispatch request #72 queued for flower.

    agent · flower-orchestrator
  9. status change 1d ago
    agent · flower-orchestrator
  10. link added 1d ago
    agent · flower-orchestrator
  11. status change 1d ago
    agent · flower-orchestrator
  12. refinement 1d ago

    ## DECIDED (operator:mike, 2026-07-04 — authoritative; supersedes the earlier "leans") All three round-1 questions answered: 1. **Home:** ONE shared idempotent `bin/prepare-worktree.sh` (superset of `setup-worktree-db.sh`), called by BOTH the app spawn-path AND worktree-manager's post-create hook — single source of truth. 2. **Prep scope:** `composer install` + `.env` key-backfill + DB/Redis isolation + `npm ci` **+ `npm run build`** — operator chose the FULL asset build in every worktree (accepts the per-dispatch build cost; do NOT skip build). 3. **On prep failure:** **block the spawn + surface to the orchestrator** — never spawn a doomed worker. ## Goal When a task is dispatched to an agent in a worktree, a server-side pre-spawn step (fired from the dispatch/spawn path, NOT carried by the agent or daemon) solidifies the worktree environment BEFORE the worker's first turn. Fast no-op when nothing drifted. ## Problem — three drift classes, one root cause worktree-manager copies MAIN's `.env` at create time and `bin/setup-worktree-db.sh` isolates DB+Redis, but everything else drifts AFTER creation as MAIN's lockfiles/config move: - **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), **#120 (this session, foundation — had to composer-install mid-task)**, sessions 860/863, #91's worker (3293). - **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d/built. - **`.env` key drift** — MAIN gains keys not backfilled into the worktree copy (CLAUDE.md env-drift gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → silent worktree-vs-MAIN divergence (the worst class because it's silent). ## Build direction 1. **Idempotent `bin/prepare-worktree.sh`** (superset of `setup-worktree-db.sh`). For a given worktree: (a) ensure branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` + `npm run build` **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare → **fast no-op when nothing drifted** → safe to run on every dispatch. 2. **Server-side pre-spawn gate.** Insert a "prepare worktree → then spawn" step in the dispatch path — the `DispatchService` `$spawn` seam (`Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22) — synchronous + idempotent, so the env is guaranteed ready before the worker's first turn. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker. 3. **One source of truth.** The generic reconcile logic lives in the shell script; both the app spawn-path and worktree-manager's post-create hook call the same thing. ## Reuse / substrate (do NOT rebuild) - `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate. - `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing. - worktree-manager (shared Go CLI) post-create hook slot. ## Out of scope - Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve). - The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here. ## Acceptance - A dispatch into a drifted worktree reconciles (composer + `.env` + `npm ci`/`npm run build`) BEFORE the worker starts; a fresh/clean worktree is a fast no-op (stamp hit). Prep outcome recorded on the `dispatch_request`; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected→runs, no-drift→skips, failure→blocks. `php artisan test` green + `pint`. `Brief: #184` trailer. ## Provenance Operator note 2026-07-04 (post-#119) → orchestrator grounding; operator answered the 3 design questions 2026-07-04 03:19–03:20. `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119/#120. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.

    agent · flower-orchestrator
  13. spec snapshot 1d ago

    ## Idea (operator, 2026-07-04) Prompted by the #119 vendor-drift flag. When a task is pushed to an agent in a worktree, an automated step — fired from an MCP action / hook, **not** carried by the agent or daemon — should solidify the worktree environment (composer install, etc.) BEFORE the worker's first turn. ## Problem — three drift classes, one root cause worktree-manager copies MAIN's `.env` at create time, and `bin/setup-worktree-db.sh` (already "intended as a worktree-manager post-create hook") isolates DB + Redis. But everything else drifts AFTER creation because MAIN's lockfiles/config move and the worktree snapshot doesn't: - **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), plus sessions 860, 863, and #91's worker (3293) all hit "vendor missing / FluxUI not installed". - **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d. - **`.env` key drift** — MAIN gains env keys not backfilled into the worktree copy (CLAUDE.md "env drift" gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → env-sensitive config/tests pass in the worktree but fail on MAIN. Arguably the *worst* class because it's silent. Today the mitigation is tribal: each worker/orchestrator must remember to check and reconcile. That is exactly the responsibility the operator wants moved into the environment. ## Direction (proposed) 1. **Idempotent `bin/prepare-worktree.sh`** — a superset of `setup-worktree-db.sh`. For a given worktree it: (a) ensures branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` (build TBD, see Q2) **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare -> **fast no-op when nothing drifted**, so it is safe to run on every dispatch. 2. **Wire it as a server-side pre-spawn gate in the dispatch path.** flower's spawn is orchestrator-mediated through `DispatchService` (the `$spawn` closure: `Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22). Insert a "prepare worktree -> then spawn" step there — synchronous + idempotent — so the env is guaranteed ready before the worker's first turn. This is the MCP-action-as-hook the operator asked for: it fires from `brief_dispatch`/spawn, not from the agent. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker (see Q3). 3. **One source of truth.** Keep the generic reconcile logic in the shell script so BOTH the app spawn-path AND worktree-manager's post-create hook call the same thing (see Q1). ## Recommendations (my leans, up for debate in the questions) - Trigger **just before spawn** (freshest; "before they start"), synchronous + idempotent-fast — not create-time only (drift happens later) and not async. - Prep scope: **composer install + `.env` key-backfill + DB/Redis** by default; **npm ci without build** (workers sqlite-test and never serve — build/serve stays MAIN's job at merge time, which already works). Revisit if a worker actually needs built assets. - Failure = **block + surface**, never spawn into a known-broken env. ## Reuse / substrate (do NOT rebuild) - `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate. - `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing. - worktree-manager (shared Go CLI) post-create hook slot. ## Out of scope - Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve). - The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here. ## Acceptance (sketch, pending answers) - A dispatch into a drifted worktree reconciles (composer/.env at minimum) BEFORE the worker starts; a fresh worktree is a fast no-op (stamp hit). Prep outcome recorded on the dispatch_request; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected->runs, no-drift->skips, failure->blocks. `php artisan test` green + pint. `Brief: #184` trailer. ## Provenance Operator note 2026-07-04 (post-#119) -> orchestrator grounding (this session): `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.

    system · flower-orchestrator
  14. operator answer 1d ago

    {"choice":"Shared bin/prepare-worktree.sh called by BOTH the app spawn-path and worktree-manager (one source of truth) — recommended"}

    operator · operator:mike
  15. operator answer 1d ago

    {"choice":"Add npm run build too (full asset build in every worktree)"}

    operator · operator:mike
  16. operator answer 1d ago

    {"choice":"Block the spawn + surface to the orchestrator — never spawn a doomed worker (recommended)"}

    operator · operator:mike
  17. participant joined 1d ago
    system · operator:mike
  18. agent question 1d ago

    If prep fails (e.g. composer install errors), what should happen?

    agent · flower-orchestrator
  19. agent question 1d ago

    What should the pre-spawn prep do by default?

    agent · flower-orchestrator
  20. agent question 1d ago

    Given you want it hook-triggered, where should the reconcile logic live?

    agent · flower-orchestrator
  21. plan proposed 1d ago

    ## Idea (operator, 2026-07-04) Prompted by the #119 vendor-drift flag. When a task is pushed to an agent in a worktree, an automated step — fired from an MCP action / hook, **not** carried by the agent or daemon — should solidify the worktree environment (composer install, etc.) BEFORE the worker's first turn. ## Problem — three drift classes, one root cause worktree-manager copies MAIN's `.env` at create time, and `bin/setup-worktree-db.sh` (already "intended as a worktree-manager post-create hook") isolates DB + Redis. But everything else drifts AFTER creation because MAIN's lockfiles/config move and the worktree snapshot doesn't: - **vendor/ drift** — MAIN's `composer.lock` gains deps (e.g. livewire/flux) never `composer install`ed in the worktree → `flux:*` tags don't resolve, tests fail, the worker firefights. Recurring: #119 (wt1), plus sessions 860, 863, and #91's worker (3293) all hit "vendor missing / FluxUI not installed". - **node_modules / build drift** — `package-lock.json` changes not `npm ci`'d. - **`.env` key drift** — MAIN gains env keys not backfilled into the worktree copy (CLAUDE.md "env drift" gotcha; the `HORIZON_LONG_LOCAL_MAX_PROCESSES` incident) → env-sensitive config/tests pass in the worktree but fail on MAIN. Arguably the *worst* class because it's silent. Today the mitigation is tribal: each worker/orchestrator must remember to check and reconcile. That is exactly the responsibility the operator wants moved into the environment. ## Direction (proposed) 1. **Idempotent `bin/prepare-worktree.sh`** — a superset of `setup-worktree-db.sh`. For a given worktree it: (a) ensures branch/base; (b) `composer install` **iff** `composer.lock` hash != a per-worktree stamp; (c) `npm ci` (build TBD, see Q2) **iff** `package-lock.json` hash changed; (d) backfill new `.env` keys from MAIN's `.env`, **never** clobbering worktree-specific `DB_DATABASE`/`REDIS_PREFIX`; (e) DB/Redis isolation (fold in the existing script). Stamp-compare -> **fast no-op when nothing drifted**, so it is safe to run on every dispatch. 2. **Wire it as a server-side pre-spawn gate in the dispatch path.** flower's spawn is orchestrator-mediated through `DispatchService` (the `$spawn` closure: `Closure(packet, routing): ?int`) + the app-side `SpawnDaemonBridge`/`SoloClient` (#22). Insert a "prepare worktree -> then spawn" step there — synchronous + idempotent — so the env is guaranteed ready before the worker's first turn. This is the MCP-action-as-hook the operator asked for: it fires from `brief_dispatch`/spawn, not from the agent. Record the prep outcome (`prepared_at`, what ran, duration, ok/failed) on the `dispatch_request`; on failure **block the spawn and surface to the orchestrator** rather than spawning a doomed worker (see Q3). 3. **One source of truth.** Keep the generic reconcile logic in the shell script so BOTH the app spawn-path AND worktree-manager's post-create hook call the same thing (see Q1). ## Recommendations (my leans, up for debate in the questions) - Trigger **just before spawn** (freshest; "before they start"), synchronous + idempotent-fast — not create-time only (drift happens later) and not async. - Prep scope: **composer install + `.env` key-backfill + DB/Redis** by default; **npm ci without build** (workers sqlite-test and never serve — build/serve stays MAIN's job at merge time, which already works). Revisit if a worker actually needs built assets. - Failure = **block + surface**, never spawn into a known-broken env. ## Reuse / substrate (do NOT rebuild) - `bin/setup-worktree-db.sh` (DB/Redis isolation + `.env` DB rewrite) — extend, don't duplicate. - `DispatchService` `$spawn` seam + `SpawnDaemonBridge`/`SpawnPacketService`/`SoloClient` (#22) — the existing spawn plumbing. - worktree-manager (shared Go CLI) post-create hook slot. ## Out of scope - Full per-worktree Horizon/serve stand-up (worktrees deliberately don't run daemons/serve). - The perpetually-dirty `M bin/setup-worktree-db.sh` in MAIN's tree (a prior "expected artifact") — minor separate hygiene item; note it, don't fix here. ## Acceptance (sketch, pending answers) - A dispatch into a drifted worktree reconciles (composer/.env at minimum) BEFORE the worker starts; a fresh worktree is a fast no-op (stamp hit). Prep outcome recorded on the dispatch_request; a failed prep blocks the spawn and surfaces to the orchestrator. Tests for: drift-detected->runs, no-drift->skips, failure->blocks. `php artisan test` green + pint. `Brief: #184` trailer. ## Provenance Operator note 2026-07-04 (post-#119) -> orchestrator grounding (this session): `setup-worktree-db.sh` already exists as a DB/Redis post-create hook; `DispatchService.$spawn` is the natural pre-spawn seam; recurring vendor-drift in sessions 860/863/3293/#119. Related: #22 (spawn bridge), worktree-manager CLI, CLAUDE.md env-drift gotcha.

    agent · flower-orchestrator
  22. note added 1d ago

    Operator idea (2026-07-04, prompted by the #119 vendor-drift flag): when we push a task to an agent in a worktree, should an automated script — triggered from an MCP action / hook, so the agent and daemons don't carry the responsibility — solidify the worktree environment (composer install, etc.) BEFORE the worker starts? The #119 wt1 worker discovered livewire/flux wasn't vendored and had to composer-install mid-task. Orchestrator to brief with thoughts / direction / questions.

    agent · flower-orchestrator
  23. participant joined 1d ago
    system · flower-orchestrator

epic · dependencies

Relationships

epic parent

depends on

No dependencies — dispatchable once planned.

agents · waves

Participants

  • flower-orchestrator participant · active
  • operator:mike participant · active
  • flower-184 participant · active
  • system:commit-trailer participant · active

trace · graph

Links

  • Commit #2210 execution
  • Scratchpad #381 execution

scope

Projects

  • flower · primary

dogfood · read-only

Agent’s-eye view

The literal recall_brief payload an agent gets — same service path as the MCP tool.