@osfactory/har 0.37.0 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -153,7 +153,7 @@ Document and configure ports in `.har/harness.env` and `.har/README.md`. Use the
153
153
  | Mailpit | `HARNESS_MAILPIT_*_PORT_DEFAULT` | Scan configured ranges |
154
154
  | Headless browser | `HARNESS_BROWSER_PORT_DEFAULT` | Scan configured ranges |
155
155
 
156
- Set slot limits in `.har/stages.json` (`agentSlots`) based on machine capacity. `har env maintain --finalize` syncs legacy `HARNESS_AGENT_SLOT_*` exports in `harness.env`.
156
+ Onboarding may set an initial `agentSlots.max` in `.har/stages.json`. After you know the real stack cost, **re-tune** that limit for machine capacity (lighter stack → you can raise; heavy Docker/DB → lower). `har env maintain --finalize` syncs legacy `HARNESS_AGENT_SLOT_*` exports in `harness.env`.
157
157
 
158
158
  **Port / infra checklist:**
159
159
 
@@ -11,7 +11,7 @@ har env status # or ./.har/agent-cli.sh 1 status
11
11
  ```
12
12
 
13
13
  - One slot ≈ one task. If slot 1 is occupied by unrelated work, use slot 2+ (limit: `agentSlots` in `.har/stages.json`).
14
- - Never replace an occupied slot (`--replace`/`--force`) without the user explicitly approving it.
14
+ - Occupied slots always block a new launch: run `har env teardown <id>` (or `complete <id>`) first, then launch again.
15
15
 
16
16
  ## 2. Launch — BEFORE editing any file
17
17
 
@@ -19,7 +19,7 @@ har env status # or ./.har/agent-cli.sh 1 status
19
19
  har env launch <id> # or ./.har/launch.sh <id>
20
20
  ```
21
21
 
22
- Launch creates a fresh session worktree from HEAD and prints its **work dir** (also recorded in `.har/slots/agent-<id>.json`, path like `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>`). If a previous launch failed partway, retry with `--resume` instead of replacing.
22
+ Launch creates a fresh session worktree from HEAD and prints its **work dir** (also recorded in `.har/slots/agent-<id>.json`, path like `~/worktrees/<base>-<sha4>-har-agent-<id>-<rand4>`). If a previous launch failed partway, retry with `--resume` instead of starting fresh.
23
23
 
24
24
  If the task already has a stable issue or ticket ID, bind it without changing the
25
25
  methodology that produced it:
@@ -15,20 +15,19 @@ work around it with ad-hoc commands.
15
15
 
16
16
  ## Before making changes
17
17
 
18
- 1. **On the main checkout, switch to the intended base** (usually `main`) — every launch creates a worktree from that HEAD. `--replace` does not select the base.
18
+ 1. **On the main checkout, switch to the intended base** (usually `main`) — every launch creates a worktree from that HEAD.
19
19
  2. **Launch your slot FIRST — before editing any file.** Use `har_launch_environment` with `agentId: 1` (MCP) or `har env launch 1`. Launch always creates a **fresh session worktree** and returns its **work dir**. If the task binds to a durable external work item (a GitHub issue, a Linear ticket, a task from the user's tracker), pass `workUnitId` + `title` on launch (`har_launch_environment` `workUnitId`/`title`, or CLI `--work-id`/`--work-title`) so Mission Control attributes usage and events to that work item instead of leaving them session-only.
20
20
  3. **Make ALL file edits under the returned work dir — never in the main checkout.** The work dir looks like `~/worktrees/<base-branch>-<sha4>-har-agent-<id>-<rand4>` (recorded in `.har/slots/agent-<id>.json`). Edits there hot-reload in the running slot; use `har env restart <id>` or `./.har/agent-cli.sh <id> restart` if a change doesn't take.
21
21
  4. Read [AGENT.md](./AGENT.md) at the repo root
22
22
  5. Read [.har/README.md](./.har/README.md) and [.har/stages.json](./.har/stages.json)
23
23
  6. After launch, read [.har/CLAUDE.agent.md](./.har/CLAUDE.agent.md) for slot URLs and definition of done
24
24
 
25
- When a slot is occupied: prefer `complete` / `teardown` if the previous task is finished, then launch. Use `--replace` / `confirmReplace=true` only to reuse the same slot id immediately (destroys the old worktree; the old branch is kept if committed). If the previous session has uncommitted changes, you must also pass `force`/`--force` after **explicit user approval** — never autonomously. Failed launches: `--resume` / `recover`, not `--replace`.
25
+ When a slot is occupied: it always blocks a new launch. Run `complete` / `teardown` on that slot id first, then `launch` again. Occupied slots do not choose the base branch either way — switch the main checkout to your intended base before launching. Failed/starting launches can be resumed instead: `--resume` / `recover`.
26
26
 
27
27
  ### Slot safety (read before launch)
28
28
 
29
29
  - **One slot ≈ one task** — do not share slot 1 across unrelated chats; use slot 2+ for parallel work.
30
30
  - **Call `har_get_status` / `har env status`** before launch when a slot may be occupied.
31
- - **Never** set `force`/`confirmReplace` without the user explicitly approving replacement.
32
31
  - **Commit early and often** in the session worktree — teardown keeps the branch, not uncommitted work.
33
32
  - **Gitignored paths are ephemeral** (`state/`, `runs/`, local clones) — they are lost when the worktree is removed.
34
33
  - With telemetry on (default, including prompts), Mission Control derives session purpose from the first captured user prompt.
@@ -12,7 +12,7 @@
12
12
  | **Database** | `agent_${AGENT_ID}` on `localhost:${DB_PORT}` (when `db` is in `HARNESS_INFRA_SERVICES`) |
13
13
  | **Work dir** | Fresh session worktree per launch — see the launch output or `.har/slots/agent-${AGENT_ID}.json` |
14
14
 
15
- **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Edits there hot-reload in the running slot; use `./.har/agent-cli.sh ${AGENT_ID} restart` if a change doesn't take. Relaunching replaces the session (branch kept) and requires explicit confirmation; dirty worktrees also need `--force` after user approval.
15
+ **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Edits there hot-reload in the running slot; use `./.har/agent-cli.sh ${AGENT_ID} restart` if a change doesn't take. An occupied slot always blocks a new launch — run `./.har/teardown.sh ${AGENT_ID}` (or `complete`) first, then launch again.
16
16
 
17
17
  This slot runs **only the primary application** (`HARNESS_PRIMARY_APP`). External dependencies and any supporting services run once, shared by all slots — `setup-infra.sh` manages them; never start them yourself.
18
18
 
@@ -187,13 +187,13 @@ Switch that checkout to your intended base before launch. The session is recorde
187
187
  the work dir through it. Make ALL file edits under the work dir printed by launch,
188
188
  never in the main checkout.
189
189
 
190
- - Prefer `complete` / `teardown` when a task is finished, then launch.
191
- - `--replace` destroys the previous worktree (requires confirmation); it does **not**
192
- choose `main`. Uncommitted changes also need `--force` after explicit user approval.
190
+ - Occupied slots always block a new launch: `har env complete <id>` (or `teardown <id>`),
191
+ then `har env launch <id>`. A new launch never chooses `main` for you — switch the
192
+ main checkout to your intended base first.
193
193
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
194
194
  or open a PR (`--delete-branch` to drop it).
195
195
  - If launch fails after creating a worktree/env file, the registry records `status: failed`.
196
- Resume without `--replace`: `har env launch <id> --resume` or `har env recover <id>`.
196
+ Resume it instead of starting fresh: `har env launch <id> --resume` or `har env recover <id>`.
197
197
  - `har env complete <id>` finishes a session: full verify (recorded as a validation),
198
198
  then teardown — branch kept.
199
199
  - `--no-worktree` runs the slot from the repo root instead (single-agent mode).
@@ -103,7 +103,7 @@ process.stdin.on('end', () => {
103
103
  exit 1
104
104
  fi
105
105
  if [ -z "$ENV_FILE" ]; then
106
- echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — relaunch with --replace when ready." >&2
106
+ echo "ERROR: .env.agent.${AGENT_ID} could not be resolved — teardown this slot, then relaunch." >&2
107
107
  exit 1
108
108
  fi
109
109
  elif [ -n "$ENV_FILE" ] && [ -f "$ENV_FILE" ]; then
@@ -245,7 +245,7 @@ har_resume_session_assignments() {
245
245
  local status
246
246
  status="$(slot_session_status "$agent_id")"
247
247
  echo "echo \"ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting).\" >&2" >&2
248
- echo "echo \" Use a normal launch, or --replace to start a fresh session.\" >&2"
248
+ echo "echo \" Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}.\" >&2"
249
249
  echo 'exit 2'
250
250
  return 1
251
251
  fi
@@ -326,36 +326,27 @@ har_regenerate_agent_env_file() {
326
326
  fi
327
327
  }
328
328
 
329
- # har_launch_preflight <agent_id> <force> <replace> [resume]
330
- # Exit 0 when ready; 2 when occupied/replace required; 1 on machine blockers.
329
+ # har_launch_preflight <agent_id> [resume]
330
+ # Exit 0 when ready; 2 when occupied (free the slot first) or not resumable; 1 on machine blockers.
331
331
  # Sets FE_PORT/API_PORT/DEBUG_PORT when this harness uses PM2.
332
332
  har_launch_preflight() {
333
333
  local agent_id="$1"
334
- local force="${2:-false}"
335
- local replace="${3:-false}"
336
- local resume="${4:-false}"
334
+ local resume="${2:-false}"
337
335
 
338
336
  if [ "$resume" = true ]; then
339
337
  if ! slot_is_resumable "$agent_id"; then
340
338
  local status
341
339
  status="$(slot_session_status "$agent_id")"
342
340
  echo "ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting)." >&2
343
- echo " Use a normal launch, or --replace to start a fresh session." >&2
341
+ echo " Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
344
342
  return 2
345
343
  fi
346
344
  echo "==> [agent-${agent_id}] Resuming partial launch (worktree and deps preserved)..." >&2
347
345
  elif slot_is_occupied "$agent_id"; then
348
- if [ "$replace" != true ] && [ "${HAR_CONFIRM_REPLACE:-}" != "1" ]; then
349
- print_slot_replace_warning "$agent_id"
350
- echo "ERROR: slot ${agent_id} is occupied — pass --replace to proceed." >&2
351
- return 2
352
- fi
353
- local wt
354
- wt="$(existing_slot_worktree "$agent_id")"
355
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
356
- echo "ERROR: dirty worktree requires --force after explicit user approval." >&2
357
- return 2
358
- fi
346
+ print_slot_occupied_warning "$agent_id"
347
+ echo "ERROR: slot ${agent_id} is occupied." >&2
348
+ echo " Free it first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
349
+ return 2
359
350
  fi
360
351
 
361
352
  if har_harness_uses_pm2; then
@@ -517,8 +508,8 @@ slot_dirty_summary() {
517
508
  echo "dirty (${count} changed)"
518
509
  }
519
510
 
520
- # Print a warning before replacing an occupied slot (stdout — visible to agents).
521
- print_slot_replace_warning() {
511
+ # Print details about an occupied slot (stdout — visible to agents).
512
+ print_slot_occupied_warning() {
522
513
  local agent_id="$1"
523
514
  local reg wt branch work_dir created status last_error dirty_summary head
524
515
  reg="$(slot_registry_file "$agent_id")"
@@ -534,7 +525,7 @@ print_slot_replace_warning() {
534
525
  fi
535
526
 
536
527
  echo "" >&2
537
- echo "⚠ Slot ${agent_id} is already in use — replacing will REMOVE the worktree." >&2
528
+ echo "⚠ Slot ${agent_id} is already in use." >&2
538
529
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
539
530
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
540
531
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
@@ -550,54 +541,21 @@ print_slot_replace_warning() {
550
541
  base_sha="$(git -C "$base_root" rev-parse --short HEAD 2>/dev/null || true)"
551
542
  echo " New session will be based on: ${base_branch}${base_sha:+ @ ${base_sha}}" >&2
552
543
  echo " (HEAD of ${base_root})" >&2
553
- echo " --replace does not choose this base — switch that checkout first for a new unrelated task." >&2
544
+ echo " Switch that checkout to your intended base before launching a new session." >&2
554
545
  echo "" >&2
555
546
  fi
556
- echo " Prefer complete/teardown when the previous task is finished, then launch." >&2
557
- echo " The session branch is kept only if you committed. Gitignored paths" >&2
558
- echo " (state/, runs/, local clones, .env.local) are NOT preserved." >&2
559
- echo "" >&2
560
- }
561
-
562
- # Require explicit confirmation before replacing an occupied slot.
563
- # $1 agent_id $2 force(true/false) $3 replace(true/false)
564
- # Exits 1 when replacement must not proceed.
565
- require_slot_replace_confirm() {
566
- local agent_id="$1"
567
- local force="$2"
568
- local replace="$3"
569
- local wt
570
-
571
- slot_is_occupied "$agent_id" || return 0
572
-
573
- print_slot_replace_warning "$agent_id"
574
- wt="$(existing_slot_worktree "$agent_id")"
575
-
576
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
577
- echo "ERROR: previous session for slot ${agent_id} has uncommitted changes." >&2
578
- echo " Commit them in the worktree (branch is kept on teardown), or pass --force to discard." >&2
579
- echo " --force destroys uncommitted work — only use after explicit user approval." >&2
580
- exit 1
581
- fi
582
-
583
- if [ "$replace" = true ] || [ "${HAR_CONFIRM_REPLACE:-}" = "1" ]; then
584
- return 0
547
+ if [ "$dirty_summary" != "clean" ] && [ "$dirty_summary" != "unknown" ]; then
548
+ echo " Commit or discard changes in the worktree, then teardown/complete the slot:" >&2
549
+ else
550
+ echo " Free the slot, then launch again:" >&2
585
551
  fi
586
-
587
- if [ -t 0 ] && [ -t 1 ]; then
588
- local answer
589
- read -r -p "Replace slot ${agent_id}? Uncommitted work is lost unless committed. [y/N] " answer
590
- if [[ "$answer" =~ ^[Yy]$ ]]; then
591
- return 0
592
- fi
593
- echo "Aborted — slot ${agent_id} left unchanged." >&2
594
- exit 2
595
- fi
596
-
597
- echo "ERROR: slot ${agent_id} is occupied." >&2
598
- echo " Pass --replace (or set HAR_CONFIRM_REPLACE=1) after reviewing the warning above." >&2
599
- echo " If the worktree is dirty, also pass --force (only after explicit user approval)." >&2
600
- exit 2
552
+ echo " har env complete ${agent_id} # or: har env teardown ${agent_id}" >&2
553
+ echo " har env launch ${agent_id}" >&2
554
+ echo "" >&2
555
+ echo " complete/teardown remove the worktree. The session branch is kept only if" >&2
556
+ echo " you committed. Gitignored paths (state/, runs/, local clones, .env.local)" >&2
557
+ echo " are NOT preserved." >&2
558
+ echo "" >&2
601
559
  }
602
560
 
603
561
  git_common_dir() {
@@ -2,8 +2,9 @@
2
2
  # Launches a single agent instance with isolated ports, database, and PM2 processes.
3
3
  # Every launch starts a FRESH session: any previous session for the slot is torn
4
4
  # down (its branch is kept) and a new suffixed worktree is created from HEAD.
5
+ # Occupied slots always block: run ./.har/teardown.sh (or complete it) first, then relaunch.
5
6
  #
6
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume]
7
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--claude] [--resume]
7
8
  set -euo pipefail
8
9
 
9
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -17,8 +18,6 @@ source "$SCRIPT_DIR/agent-slot.sh"
17
18
  AGENT_ID="${1:-}"
18
19
  USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
20
  USE_CLAUDE=false
20
- FORCE=false
21
- REPLACE=false
22
21
  RESUME=false
23
22
  WORK_UNIT_ID="${HAR_WORK_UNIT_ID:-}"
24
23
  ATTEMPT_ID="${HAR_ATTEMPT_ID:-}"
@@ -28,8 +27,6 @@ for arg in "$@"; do
28
27
  --no-worktree) USE_WORKTREE=false ;;
29
28
  --worktree) USE_WORKTREE=true ;;
30
29
  --claude) USE_CLAUDE=true ;;
31
- --replace) REPLACE=true ;;
32
- --force) FORCE=true ;;
33
30
  --resume) RESUME=true ;;
34
31
  --work-id=*) WORK_UNIT_ID="${arg#*=}" ;;
35
32
  --attempt-id=*) ATTEMPT_ID="${arg#*=}" ;;
@@ -44,7 +41,7 @@ export SLOT_ATTEMPT_ID="$ATTEMPT_ID"
44
41
 
45
42
  if [[ -z "$AGENT_ID" ]]; then
46
43
  har_load_agent_slot_limits
47
- echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--replace] [--force] [--resume] " >&2
44
+ echo "Usage: $0 <agent-id> [--no-worktree] [--claude] [--resume]" >&2
48
45
  echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
49
46
  exit 1
50
47
  fi
@@ -63,7 +60,7 @@ ENV_FILE=""
63
60
  REGISTRY_WRITTEN=false
64
61
 
65
62
  if [ "$RESUME" = true ]; then
66
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" true || exit $?
63
+ har_launch_preflight "$AGENT_ID" true || exit $?
67
64
  eval "$(har_resume_session_assignments "$AGENT_ID")"
68
65
  REGISTRY_WRITTEN=true
69
66
  mark_slot_failed() {
@@ -92,14 +89,8 @@ if [ "$RESUME" = true ]; then
92
89
  trap mark_slot_failed EXIT
93
90
  else
94
91
  # Preflight before worktree/install — ports, foreign PM2, Docker, occupied slot.
95
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" || exit $?
96
-
97
- # Replace any previous session for this slot — requires explicit confirmation.
98
- if slot_is_occupied "$AGENT_ID"; then
99
- require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
100
- log "Replacing previous session for slot ${AGENT_ID}..."
101
- "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
102
- fi
92
+ # Occupied slots always block here: teardown/complete first, then relaunch.
93
+ har_launch_preflight "$AGENT_ID" || exit $?
103
94
  fi
104
95
 
105
96
  if har_harness_uses_pm2; then
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Launch readiness gate — run before launch.sh or standalone.
3
- # Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
3
+ # Usage: ./.har/preflight.sh <agent-id>
4
4
  # JSON: har env preflight <id> --json
5
5
  set -euo pipefail
6
6
 
@@ -13,24 +13,15 @@ source "$SCRIPT_DIR/harness.env"
13
13
  source "$SCRIPT_DIR/agent-slot.sh"
14
14
 
15
15
  AGENT_ID="${1:-}"
16
- FORCE=false
17
- REPLACE=false
18
-
19
- for arg in "$@"; do
20
- case "$arg" in
21
- --replace) REPLACE=true ;;
22
- --force) FORCE=true ;;
23
- esac
24
- done
25
16
 
26
17
  if [[ -z "$AGENT_ID" ]]; then
27
- echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
18
+ echo "Usage: $0 <agent-id>" >&2
28
19
  exit 1
29
20
  fi
30
21
 
31
22
  validate_agent_id "$AGENT_ID"
32
23
 
33
- if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
24
+ if har_launch_preflight "$AGENT_ID"; then
34
25
  echo "Slot ${AGENT_ID}: ready to launch."
35
26
  if har_harness_uses_pm2; then
36
27
  echo " Ports: frontend=${FE_PORT} api=${API_PORT} debug=${DEBUG_PORT}"
@@ -9,7 +9,7 @@
9
9
  | **Agent ID** | ${AGENT_ID} |
10
10
  | **Work dir** | Fresh session worktree per launch — see the launch output or `.har/slots/agent-${AGENT_ID}.json` |
11
11
 
12
- **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Relaunching replaces the session (branch kept) and requires explicit confirmation (`--replace` / `confirmReplace`); dirty worktrees also need `--force` after user approval — never autonomously.
12
+ **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. An occupied slot always blocks a new launch — run `har env teardown <id>` (or `complete <id>`) first, then launch again.
13
13
 
14
14
  ```bash
15
15
  ./.har/agent-cli.sh ${AGENT_ID} status
@@ -145,9 +145,9 @@ Switch that checkout to your intended base before launch. The session is recorde
145
145
  the work dir through it. Make ALL file edits under the work dir printed by launch,
146
146
  never in the main checkout.
147
147
 
148
- - Prefer `complete` / `teardown` when a task is finished, then launch.
149
- - `--replace` destroys the previous worktree (requires confirmation); it does **not**
150
- choose `main`. Uncommitted changes also need `--force` after explicit user approval.
148
+ - Occupied slots always block a new launch: `har env complete <id>` (or `teardown <id>`),
149
+ then `har env launch <id>`. A new launch never chooses `main` for you — switch the
150
+ main checkout to your intended base first.
151
151
  - `teardown` removes the worktree but **keeps the session branch** so you can push it
152
152
  or open a PR (`--delete-branch` to drop it).
153
153
  - If launch fails after creating a worktree/env file, resume with
@@ -223,7 +223,7 @@ har_resume_session_assignments() {
223
223
  local status
224
224
  status="$(slot_session_status "$agent_id")"
225
225
  echo "echo \"ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting).\" >&2" >&2
226
- echo "echo \" Use a normal launch, or --replace to start a fresh session.\" >&2"
226
+ echo "echo \" Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}.\" >&2"
227
227
  echo 'exit 2'
228
228
  return 1
229
229
  fi
@@ -310,33 +310,26 @@ EOF
310
310
  fi
311
311
  }
312
312
 
313
+ # har_launch_preflight <agent_id> [resume]
314
+ # Exit 0 when ready; 2 when occupied (free the slot first) or not resumable; 1 on machine blockers.
313
315
  har_launch_preflight() {
314
316
  local agent_id="$1"
315
- local force="${2:-false}"
316
- local replace="${3:-false}"
317
- local resume="${4:-false}"
317
+ local resume="${2:-false}"
318
318
 
319
319
  if [ "$resume" = true ]; then
320
320
  if ! slot_is_resumable "$agent_id"; then
321
321
  local status
322
322
  status="$(slot_session_status "$agent_id")"
323
323
  echo "ERROR: slot ${agent_id} is not resumable (status=${status:-none}; need failed or starting)." >&2
324
- echo " Use a normal launch, or --replace to start a fresh session." >&2
324
+ echo " Free the slot first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
325
325
  return 2
326
326
  fi
327
327
  echo "==> [agent-${agent_id}] Resuming partial launch (worktree and deps preserved)..." >&2
328
328
  elif slot_is_occupied "$agent_id"; then
329
- if [ "$replace" != true ] && [ "${HAR_CONFIRM_REPLACE:-}" != "1" ]; then
330
- print_slot_replace_warning "$agent_id"
331
- echo "ERROR: slot ${agent_id} is occupied — pass --replace to proceed." >&2
332
- return 2
333
- fi
334
- local wt
335
- wt="$(existing_slot_worktree "$agent_id")"
336
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
337
- echo "ERROR: dirty worktree requires --force after explicit user approval." >&2
338
- return 2
339
- fi
329
+ print_slot_occupied_warning "$agent_id"
330
+ echo "ERROR: slot ${agent_id} is occupied." >&2
331
+ echo " Free it first: har env teardown ${agent_id} (or complete ${agent_id}), then har env launch ${agent_id}." >&2
332
+ return 2
340
333
  fi
341
334
 
342
335
  if har_harness_uses_pm2; then
@@ -497,8 +490,8 @@ slot_dirty_summary() {
497
490
  echo "dirty (${count} changed)"
498
491
  }
499
492
 
500
- # Print a warning before replacing an occupied slot (stdout — visible to agents).
501
- print_slot_replace_warning() {
493
+ # Print details about an occupied slot (stdout — visible to agents).
494
+ print_slot_occupied_warning() {
502
495
  local agent_id="$1"
503
496
  local reg wt branch work_dir created status last_error dirty_summary head
504
497
  reg="$(slot_registry_file "$agent_id")"
@@ -514,7 +507,7 @@ print_slot_replace_warning() {
514
507
  fi
515
508
 
516
509
  echo "" >&2
517
- echo "⚠ Slot ${agent_id} is already in use — replacing will REMOVE the worktree." >&2
510
+ echo "⚠ Slot ${agent_id} is already in use." >&2
518
511
  [ -n "$wt" ] && echo " Worktree: ${wt}" >&2
519
512
  [ -n "$work_dir" ] && echo " Work dir: ${work_dir}" >&2
520
513
  [ -n "$branch" ] && echo " Branch: ${branch}${head:+ @ ${head}}" >&2
@@ -530,54 +523,21 @@ print_slot_replace_warning() {
530
523
  base_sha="$(git -C "$base_root" rev-parse --short HEAD 2>/dev/null || true)"
531
524
  echo " New session will be based on: ${base_branch}${base_sha:+ @ ${base_sha}}" >&2
532
525
  echo " (HEAD of ${base_root})" >&2
533
- echo " --replace does not choose this base — switch that checkout first for a new unrelated task." >&2
526
+ echo " Switch that checkout to your intended base before launching a new session." >&2
534
527
  echo "" >&2
535
528
  fi
536
- echo " Prefer complete/teardown when the previous task is finished, then launch." >&2
537
- echo " The session branch is kept only if you committed. Gitignored paths" >&2
538
- echo " (state/, runs/, local clones, .env.local) are NOT preserved." >&2
539
- echo "" >&2
540
- }
541
-
542
- # Require explicit confirmation before replacing an occupied slot.
543
- # $1 agent_id $2 force(true/false) $3 replace(true/false)
544
- # Exits 1 when replacement must not proceed.
545
- require_slot_replace_confirm() {
546
- local agent_id="$1"
547
- local force="$2"
548
- local replace="$3"
549
- local wt
550
-
551
- slot_is_occupied "$agent_id" || return 0
552
-
553
- print_slot_replace_warning "$agent_id"
554
- wt="$(existing_slot_worktree "$agent_id")"
555
-
556
- if [ -n "$wt" ] && slot_worktree_dirty "$wt" && [ "$force" != true ]; then
557
- echo "ERROR: previous session for slot ${agent_id} has uncommitted changes." >&2
558
- echo " Commit them in the worktree (branch is kept on teardown), or pass --force to discard." >&2
559
- echo " --force destroys uncommitted work — only use after explicit user approval." >&2
560
- exit 1
561
- fi
562
-
563
- if [ "$replace" = true ] || [ "${HAR_CONFIRM_REPLACE:-}" = "1" ]; then
564
- return 0
565
- fi
566
-
567
- if [ -t 0 ] && [ -t 1 ]; then
568
- local answer
569
- read -r -p "Replace slot ${agent_id}? Uncommitted work is lost unless committed. [y/N] " answer
570
- if [[ "$answer" =~ ^[Yy]$ ]]; then
571
- return 0
572
- fi
573
- echo "Aborted — slot ${agent_id} left unchanged." >&2
574
- exit 2
529
+ if [ "$dirty_summary" != "clean" ] && [ "$dirty_summary" != "unknown" ]; then
530
+ echo " Commit or discard changes in the worktree, then teardown/complete the slot:" >&2
531
+ else
532
+ echo " Free the slot, then launch again:" >&2
575
533
  fi
576
-
577
- echo "ERROR: slot ${agent_id} is occupied." >&2
578
- echo " Pass --replace (or set HAR_CONFIRM_REPLACE=1) after reviewing the warning above." >&2
579
- echo " If the worktree is dirty, also pass --force (only after explicit user approval)." >&2
580
- exit 2
534
+ echo " har env complete ${agent_id} # or: har env teardown ${agent_id}" >&2
535
+ echo " har env launch ${agent_id}" >&2
536
+ echo "" >&2
537
+ echo " complete/teardown remove the worktree. The session branch is kept only if" >&2
538
+ echo " you committed. Gitignored paths (state/, runs/, local clones, .env.local)" >&2
539
+ echo " are NOT preserved." >&2
540
+ echo "" >&2
581
541
  }
582
542
 
583
543
  git_common_dir() {
@@ -2,8 +2,9 @@
2
2
  # Launch an agent slot for CLI/library repos (git worktree by default, optional Docker infra).
3
3
  # Every launch starts a FRESH session: any previous session for the slot is torn
4
4
  # down (its branch is kept) and a new suffixed worktree is created from HEAD.
5
+ # Occupied slots always block: run ./.har/teardown.sh (or complete it) first, then relaunch.
5
6
  #
6
- # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--replace] [--force] [--resume]
7
+ # Usage: ./.har/launch.sh <agent-id> [--no-worktree] [--resume]
7
8
  set -euo pipefail
8
9
 
9
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -16,8 +17,6 @@ source "$SCRIPT_DIR/agent-slot.sh"
16
17
 
17
18
  AGENT_ID="${1:-}"
18
19
  USE_WORKTREE="${HARNESS_USE_WORKTREE:-true}"
19
- FORCE=false
20
- REPLACE=false
21
20
  RESUME=false
22
21
  WORK_UNIT_ID="${HAR_WORK_UNIT_ID:-}"
23
22
  ATTEMPT_ID="${HAR_ATTEMPT_ID:-}"
@@ -26,8 +25,6 @@ for arg in "$@"; do
26
25
  case "$arg" in
27
26
  --no-worktree) USE_WORKTREE=false ;;
28
27
  --worktree) USE_WORKTREE=true ;;
29
- --replace) REPLACE=true ;;
30
- --force) FORCE=true ;;
31
28
  --resume) RESUME=true ;;
32
29
  --work-id=*) WORK_UNIT_ID="${arg#*=}" ;;
33
30
  --attempt-id=*) ATTEMPT_ID="${arg#*=}" ;;
@@ -42,7 +39,7 @@ export SLOT_ATTEMPT_ID="$ATTEMPT_ID"
42
39
 
43
40
  if [[ -z "$AGENT_ID" ]]; then
44
41
  har_load_agent_slot_limits
45
- echo "Usage: $0 <agent-id> [--no-worktree] [--replace] [--force] [--resume] " >&2
42
+ echo "Usage: $0 <agent-id> [--no-worktree] [--resume]" >&2
46
43
  echo " agent-id must be between ${HARNESS_AGENT_SLOT_MIN} and ${HARNESS_AGENT_SLOT_MAX}" >&2
47
44
  exit 1
48
45
  fi
@@ -61,7 +58,7 @@ ENV_FILE=""
61
58
  REGISTRY_WRITTEN=false
62
59
 
63
60
  if [ "$RESUME" = true ]; then
64
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" true || exit $?
61
+ har_launch_preflight "$AGENT_ID" true || exit $?
65
62
  eval "$(har_resume_session_assignments "$AGENT_ID")"
66
63
  REGISTRY_WRITTEN=true
67
64
  mark_slot_failed() {
@@ -87,13 +84,8 @@ if [ "$RESUME" = true ]; then
87
84
  }
88
85
  trap mark_slot_failed EXIT
89
86
  else
90
- har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE" || exit $?
91
-
92
- if slot_is_occupied "$AGENT_ID"; then
93
- require_slot_replace_confirm "$AGENT_ID" "$FORCE" "$REPLACE"
94
- log "Replacing previous session for slot ${AGENT_ID}..."
95
- "$SCRIPT_DIR/teardown.sh" "$AGENT_ID" >&2
96
- fi
87
+ # Occupied slots always block here: teardown/complete first, then relaunch.
88
+ har_launch_preflight "$AGENT_ID" || exit $?
97
89
  fi
98
90
 
99
91
  "$SCRIPT_DIR/setup-infra.sh"
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  # Launch readiness gate for CLI/library harnesses (occupied slot only).
3
- # Usage: ./.har/preflight.sh <agent-id> [--replace] [--force]
3
+ # Usage: ./.har/preflight.sh <agent-id>
4
4
  set -euo pipefail
5
5
 
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -12,24 +12,15 @@ source "$SCRIPT_DIR/harness.env"
12
12
  source "$SCRIPT_DIR/agent-slot.sh"
13
13
 
14
14
  AGENT_ID="${1:-}"
15
- FORCE=false
16
- REPLACE=false
17
-
18
- for arg in "$@"; do
19
- case "$arg" in
20
- --replace) REPLACE=true ;;
21
- --force) FORCE=true ;;
22
- esac
23
- done
24
15
 
25
16
  if [[ -z "$AGENT_ID" ]]; then
26
- echo "Usage: $0 <agent-id> [--replace] [--force]" >&2
17
+ echo "Usage: $0 <agent-id>" >&2
27
18
  exit 1
28
19
  fi
29
20
 
30
21
  validate_agent_id "$AGENT_ID"
31
22
 
32
- if har_launch_preflight "$AGENT_ID" "$FORCE" "$REPLACE"; then
23
+ if har_launch_preflight "$AGENT_ID"; then
33
24
  echo "Slot ${AGENT_ID}: ready to launch."
34
25
  exit 0
35
26
  fi
@@ -11,7 +11,7 @@
11
11
  | **Simulator** | Configured in `.har/harness.env` — `HARNESS_SIMULATOR_NAME` |
12
12
  | **Scheme** | Configured in `.har/harness.env` — `HARNESS_XCODE_SCHEME` |
13
13
 
14
- **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. Relaunching replaces the session (branch kept) and requires explicit confirmation; dirty worktrees also need `--force` after user approval.
14
+ **Never edit the main checkout** — launch FIRST, then make ALL file edits under the work dir from the launch output. An occupied slot always blocks a new launch — run `har env teardown <id>` (or `complete <id>`) first, then launch again.
15
15
 
16
16
  ```bash
17
17
  ./.har/agent-cli.sh ${AGENT_ID} status
@@ -119,8 +119,8 @@ checkout's current HEAD** at
119
119
  Switch that checkout to your intended base before launch. The session is recorded in
120
120
  `.har/slots/agent-<id>.json`.
121
121
 
122
- - Prefer `complete` / `teardown` when a task is finished, then launch.
123
- - `--replace` destroys the previous worktree; it does **not** choose `main`.
124
- Uncommitted changes also require `--force` after explicit approval.
122
+ - Occupied slots always block a new launch: `har env complete <id>` (or `teardown <id>`),
123
+ then `har env launch <id>`. A new launch never chooses `main` for you — switch the
124
+ main checkout to your intended base first.
125
125
  - `teardown` removes the worktree but **keeps the session branch** for push / PR.
126
126
  - `har env complete <id>` finishes a session: full verify + teardown, branch kept.