Skip to content

feat(bin): retain recent Done outcomes as bounded fleet memory - #1211

Closed
yelenplays wants to merge 7 commits into
kunchenguid:mainfrom
yelenplays:fm/firstmate-deck-completion-memory
Closed

feat(bin): retain recent Done outcomes as bounded fleet memory#1211
yelenplays wants to merge 7 commits into
kunchenguid:mainfrom
yelenplays:fm/firstmate-deck-completion-memory

Conversation

@yelenplays

Copy link
Copy Markdown

Intent

Implement the Firstmate-side memory source requested for Deck using the existing active backlog Done window plus durable archive split. Increase the tracked tasks-axi markdown done_keep policy from 10 to 50 and update its authoritative configuration documentation without duplicating the number elsewhere. Keep finished workers clean-up compatible while ensuring fm-fleet-snapshot still exposes recent structured Done outcomes as bounded memory after live metadata, status, and endpoints are gone. The safe Done shape should include structured title, repo, kind, completion date, and admitted artifact link, but must not expose task notes, decision bodies, report contents, raw status prose, absolute local paths, or arbitrary Done body prose. Prove tasks-axi pruning with an isolated temporary home: at 51 Done items it archives only the oldest and retains exactly the newest 50. Preserve decision holds, dependencies, archive format, Done chronology, session-start size bounds, secondmate isolation, and existing snapshot consumers. Do not edit Firstmate Deck and do not parse data/done-archive.md into Deck.

What Changed

  • Raised the tracked .tasks.toml done_keep from 10 to 50 so the active backlog keeps a 50-item recent-completion window and pruning only archives past it; bin/fm-teardown.sh and docs/configuration.md now name the done_keep setting instead of restating the number, and tests/fm-instruction-owners.test.sh pins the documented value to .tasks.toml.
  • bin/fm-fleet-snapshot.sh now emits Done records as structured identity plus artifact only - body_lines emptied and body_excerpt null, with a local main body line lifted into local_note before the drop - so recent outcomes survive a torn-down worker's meta/status/endpoint cleanup without carrying task notes, decision bodies, report prose, or absolute local paths. Unstructured Done lines keep their position but have their raw prose replaced by the fixed marker (unstructured done line).
  • Narrowed bin/fm-bearings-snapshot.sh --fields bodies to queued items only (Done rows now carry no bodies) and updated its help and omitted-surface label to match; added regression cases for 51-item pruning (archives the oldest, retains the newest 50) and for Done body suppression after worker cleanup.

Risk Assessment

✅ Low: The branch is well-bounded and every intent criterion verified end-to-end against the real tasks-axi binary and the changed scripts, and the single round-1 warning is now fixed with a one-line Done-scoped redaction that I confirmed closes the leak without touching in-flight/queued rows, main_inventory signals, or any other snapshot consumer.

Testing

Exercised the six targeted suites touching this change plus a manual end-to-end driver that reproduces the captain's actual experience. Pruning was proven in an isolated temporary home by closing 51 items through the real tasks-axi verbs fm-teardown.sh emits: exactly 50 stay in data/backlog.md newest-first, only recent-01 moves into data/done-archive.md, and the ## Archived <date> archive format is unchanged. Done-as-memory was proven by rendering bin/fm-fleet-view.sh on a home where a ship worker is live, then again after its meta, status log, worktree and tmux endpoint are removed - the worker drops out of Under Way while its Done row keeps id, title, repo, kind, completion verb/date and the admitted artifact (PR URL, data/<id>/report.md, or local main), with body_lines: [] and body_excerpt: null. The same fixture carries a task note, a decision body, status prose, absolute /Users/yelen/private/... paths and a hand-written Done line; running base commit 514f0ab against that identical home leaks all of it through the snapshot, the view and bearings --fields bodies, while the target commit is clean on all five consumer surfaces and keeps the unstructured line in place as (unstructured done line). Queued bodies are still revealed, so the redaction is not over-broad. Session-start bounding, decision holds, dependency resolution and secondmate handoff isolation all stay green, and done_keep appears only in .tasks.toml and docs/configuration.md with fm-teardown.sh now deferring to the config. No screenshot or rendered-HTML artifact applies: the change is entirely bash and markdown docs with no HTML, CSS or renderer surface, so the faithful end-user artifact is the CLI transcript of fm-fleet-view.sh / fm-fleet-snapshot.sh output, captured verbatim.

Evidence: Full E2E transcript (pruning, Done-memory-after-cleanup, base-vs-target leak check)

===============================================================================
PART 1 - tasks-axi done_keep pruning in an isolated temporary home
===============================================================================
isolated home: /var/folders/9d/8w50jhgd79x63rgbq_5cyyvm0000gn/T/no-mistakes-evidence/01KYN1F5W2FP0A3RE9T5Q88DBB/prune-home
tracked .tasks.toml in effect:
    backend = "markdown"
    
    [markdown]
    path = "data/backlog.md"
    archive = "data/done-archive.md"
    done_keep = 50

closing 51 items through the real tasks-axi verbs fm-teardown.sh emits ...

backlog.md Done rows       : 50   (expect 50)
done-archive.md rows       : 1    (expect 1)
recent-01 still in backlog : 0    (expect 0)
recent-02 pushed to archive: 0    (expect 0)

archived (oldest only):
    - [x] recent-01 - Recent outcome 1 (repo: sample) (kind: ship) (done 2026-07-28)

archive format preserved:
    
    ## Archived 2026-07-28
    - [x] recent-01 - Recent outcome 1 (repo: sample) (kind: ship) (done 2026-07-28)
      local main

retained window, Done chronology newest-first:
    - [x] recent-51 - Recent outcome 51 (repo: sample) (kind: ship) (done 2026-07-28)
    - [x] recent-50 - Recent outcome 50 (repo: sample) (kind: ship) (done 2026-07-28)
    ...
    - [x] recent-03 - Recent outcome 3 (repo: sample) (kind: ship) (done 2026-07-28)
    - [x] recent-02 - Recent outcome 2 (repo: sample) (kind: ship) (done 2026-07-28)

session-start digest bound unchanged (the exact call bin/fm-session-start.sh makes,
default FM_SESSION_START_BACKLOG_LIMIT=80):
    count: 50
    tasks[50]{id,state,kind,repo,title,blocked_by,hold_kind,hold_reason}:
      recent-51,done,ship,sample,Recent outcome 51,none,"-","-"
      recent-50,done,ship,sample,Recent outcome 50,none,"-","-"
    ... 55 lines total; "local main" note lines in the digest: 0 (expect 0)

===============================================================================
PART 2 - Done outcomes survive worker cleanup as bounded, prose-free memory
===============================================================================
data/backlog.md on disk (the captain's real file, private prose and all):
    ## In flight
    ## Queued
    - [ ] queued-delta - Refactor delta parser (repo: delta) (kind: ship) (since 2026-07-28)
      QUEUED BODY: needs the captain to pick the parser strategy first
    
    ## Done
    - [x] local-gamma - Tune gamma cache (repo: gamma) (kind: ship) (done 2026-07-28)
      local main
      TASK NOTE: credentials rotated by hand, see the private runbook
      /Users/yelen/private/local-gamma-worktree
    - [x] scout-beta - Investigate beta latency data/scout-beta/report.md (repo: beta) (kind: scout) (reported 2026-07-28)
      DECISION BODY: captain chose route north over route south, do not publish
      report says the beta p99 regression came from the internal billing shim
    - [x] ship-alpha - Fix alpha login redirect https://github.com/kunchenguid/firstmate/pull/1207 (repo: alpha) (kind: ship) (merged 2026-07-28)
      STATUS PROSE: blocked on captain, then unblocked after standup /Users/yelen/private/stranded
    hand written done line, kept for history /Users/yelen/private/free-form

===============================================================================
PART 2a - worker still live (meta + status log + tmux endpoint present)
===============================================================================
    state/ship-alpha.meta
    state/ship-alpha.status

# Fleet View

Schema: fm-fleet-snapshot.v1
Home: /var/folders/9d/8w50jhgd79x63rgbq_5cyyvm0000gn/T/no-mistakes-evidence/01KYN1F5W2FP0A3RE9T5Q88DBB/fleet-home

## Under Way
| ID | Current | Kind | Repo/Project | Backend | Endpoint | Artifact | Path | Watch / return channel |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| ship-alpha | working / pane | ship | alpha | tmux | present | https://github.com/kunchenguid/firstmate/pull/1207 | /var/folders/9d/8w50jhgd79x63rgbq_5cyyvm0000gn/T/no-mistakes-evidence/01KYN1F5W2FP0A3RE9T5Q88DBB/fleet-home/projects/alpha-worktree | bin/fm-peek.sh fm-ship-alpha |

## Queued
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| queued-delta | Refactor delta parser | delta | ship | - | - |

## Done
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| local-gamma | Tune gamma cache | gamma | ship | - | local main |
| scout-beta | Investigate beta latency | beta | scout | - | data/scout-beta/report.md |
| ship-alpha | Fix alpha login redirect | alpha | ship | - | https://github.com/kunchenguid/firstmate/pull/1207 |
| - | (unstructured done line) | - | - | - | - |

## Secondmates
For kind=secondmate, bearings selects validated structured state from that registered home; parent events and bounded terminal evidence are fallback-only supplements and never current-state authority.

===============================================================================
PART 2b - finished worker cleaned up: meta, status log and endpoint all gone
===============================================================================
    state/ is now empty; the tmux window no longer exists

# Fleet View

Schema: fm-fleet-snapshot.v1
Home: /var/folders/9d/8w50jhgd79x63rgbq_5cyyvm0000gn/T/no-mistakes-evidence/01KYN1F5W2FP0A3RE9T5Q88DBB/fleet-home

## Under Way
No live task metadata found.

## Queued
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| queued-delta | Refactor delta parser | delta | ship | - | - |

## Done
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| local-gamma | Tune gamma cache | gamma | ship | - | local main |
| scout-beta | Investigate beta latency | beta | scout | - | data/scout-beta/report.md |
| ship-alpha | Fix alpha login redirect | alpha | ship | - | https://github.com/kunchenguid/firstmate/pull/1207 |
| - | (unstructured done line) | - | - | - | - |

## Secondmates
For kind=secondmate, bearings selects validated structured state from that registered home; parent events and bounded terminal evidence are fallback-only supplements and never current-state authority.

===============================================================================
PART 2c - the retained Done shape in the structured snapshot contract
===============================================================================
[
  {
    "id": "local-gamma",
    "title": "Tune gamma cache",
    "repo": "gamma",
    "kind": "ship",
    "completion": {
      "verb": "done",
      "date": "2026-07-28"
    },
    "artifact": "local main",
    "raw": "- [x] local-gamma - Tune gamma cache (repo: gamma) (kind: ship) (done 2026-07-28)",
    "body_lines": [],
    "body_excerpt": null
  },
  {
    "id": "scout-beta",
    "title": "Investigate beta latency",
    "repo": "beta",
    "kind": "scout",
    "completion": {
      "verb": "reported",
      "date": "2026-07-28"
    },
    "artifact": "data/scout-beta/report.md",
    "raw": "- [x] scout-beta - Investigate beta latency data/scout-beta/report.md (repo: beta) (kind: scout) (reported 2026-07-28)",
    "body_lines": [],
    "body_excerpt": null
  },
  {
    "id": "ship-alpha",
    "title": "Fix alpha login redirect",
    "repo": "alpha",
    "kind": "ship",
    "completion": {
      "verb": "merged",
      "date": "2026-07-28"
    },
    "artifact": "https://github.com/kunchenguid/firstmate/pull/1207",
    "raw": "- [x] ship-alpha - Fix alpha login redirect https://github.com/kunchenguid/firstmate/pull/1207 (repo: alpha) (kind: ship) (merged 2026-07-28)",
    "body_lines": [],
    "body_excerpt": null
  },
  {
    "id": null,
    "title": null,
    "repo": null,
    "kind": null,
    "completion": null,
    "artifact": null,
    "raw": "(unstructured done line)",
    "body_lines": [],
    "body_excerpt": null
  }
]

===============================================================================
PART 2d - In flight / Queued bodies are NOT over-redacted
===============================================================================
    queued	queued-delta	body_excerpt=QUEUED BODY: needs the captain to pick the parser strategy first
    done	local-gamma	body_excerpt=null
    done	scout-beta	body_excerpt=null
    done	ship-alpha	body_excerpt=null
    done	-	body_excerpt=null

    fm-bearings-snapshot.sh --fields bodies (queued only, Done rows carry none):
    [{"id":"queued-delta","body":"QUEUED BODY: needs the captain to pick the parser strategy first"}]

===============================================================================
PART 3 - leak check on every Done-carrying consumer surface
===============================================================================
BASE commit 514f0ab (before the change), same home:
  LEAK   fm-fleet-snapshot.sh --json                          rc=0 -> /Users/yelen/private DECISION BODY STATUS PROSE TASK NOTE billing shim credentials rotated hand written done line private runbook route north 
  LEAK   fm-fleet-view.sh                                     rc=0 -> /Users/yelen/private hand written done line 
  LEAK   fm-bearings-snapshot.sh --fields bodies              rc=0 -> /Users/yelen/private DECISION BODY STATUS PROSE TASK NOTE billing shim credentials rotated private runbook route north 

TARGET commit 88327f8 (this change), same home:
  clean  fm-fleet-snapshot.sh --json                          rc=0
  clean  fm-fleet-view.sh                                     rc=0
  clean  fm-bearings-snapshot.sh                              rc=0
  clean  fm-bearings-snapshot.sh --json                       rc=0
  clean  fm-bearings-snapshot.sh --fields bodies,paths,actions,endpoints rc=0

===============================================================================
PART 4 - the number lives in exactly two places
===============================================================================
    ./bin/fm-teardown.sh:448:    printf '%s\n' "Backlog: $ID just finished. Update data/backlog.md - move $ID to Done, keep Done to the done_keep limit configured in .tasks.toml, then re-scan Queued and dispatch only work whose blockers are gone and date is due."
    ./docs/configuration.md:36:The tracked `.tasks.toml` pins the default `tasks-axi` markdown backend to `data/backlog.md`, with `done_keep = 50` and an archive at `data/done-archive.md`.
    ./.tasks.toml:6:done_keep = 50

    fm-teardown.sh manual-backend prompt no longer hardcodes a count:
    448:    printf '%s\n' "Backlog: $ID just finished. Update data/backlog.md - move $ID to Done, keep Done to the done_keep limit configured in .tasks.toml, then re-scan Queued and dispatch only work whose blockers are gone and date is due."

    Deck / archive coupling:
    data/done-archive.md referenced only by: ./docs/configuration.md ./.tasks.toml 
Evidence: Reproducible evidence driver script
#!/usr/bin/env bash
# End-to-end evidence driver for "Firstmate-side Done memory source for Deck".
# Usage: REPO=<worktree> bash run-done-memory-e2e.sh
set -eu

EV=$(cd "$(dirname "$0")" && pwd)
R=${REPO:?set REPO to the firstmate worktree}
SECRETS='PRIVATE|TASK NOTE|DECISION BODY|STATUS PROSE|route north|billing shim|private runbook|/Users/yelen/private|hand written done line|credentials rotated'

hr() { printf '\n===============================================================================\n%s\n===============================================================================\n' "$1"; }

# ---------------------------------------------------------------- part 1 ------
hr "PART 1 - tasks-axi done_keep pruning in an isolated temporary home"

P="$EV/prune-home"; rm -rf "$P"; mkdir -p "$P/data"
cp "$R/.tasks.toml" "$P/.tasks.toml"
printf '## In flight\n\n## Queued\n\n## Done\n' > "$P/data/backlog.md"
cd "$P"

echo "isolated home: $P"
echo "tracked .tasks.toml in effect:"; sed 's/^/    /' "$P/.tasks.toml"
echo
echo "closing 51 items through the real tasks-axi verbs fm-teardown.sh emits ..."
for n in $(seq 1 51); do
  k=$(printf 'recent-%02d' "$n")
  tasks-axi add "$k" "Recent outcome $n" --kind ship --repo sample >/dev/null
  tasks-axi done "$k" --note "local main" >/dev/null
done

echo
printf 'backlog.md Done rows       : %s   (expect 50)\n' "$(grep -cE '^- \[x\] recent-[0-9][0-9] -' "$P/data/backlog.md")"
printf 'done-archive.md rows       : %s    (expect 1)\n' "$(grep -cE '^- \[x\] recent-[0-9][0-9] -' "$P/data/done-archive.md")"
printf 'recent-01 still in backlog : %s    (expect 0)\n' "$(grep -cE '^- \[x\] recent-01 -' "$P/data/backlog.md")"
printf 'recent-02 pushed to archive: %s    (expect 0)\n' "$(grep -cE '^- \[x\] recent-02 -' "$P/data/done-archive.md")"
echo
echo "archived (oldest only):"; grep -E '^- \[x\] recent-' "$P/data/done-archive.md" | sed 's/^/    /'
echo
echo "archive format preserved:"; sed -n '1,5p' "$P/data/done-archive.md" | sed 's/^/    /'
echo
echo "retained window, Done chronology newest-first:"
grep -E '^- \[x\] recent-' "$P/data/backlog.md" | head -2 | sed 's/^/    /'
echo "    ..."
grep -E '^- \[x\] recent-' "$P/data/backlog.md" | tail -2 | sed 's/^/    /'
echo
echo "session-start digest bound unchanged (the exact call bin/fm-session-start.sh makes,"
echo "default FM_SESSION_START_BACKLOG_LIMIT=80):"
sslist=$(tasks-axi list --file "$P/data/backlog.md" --limit 80 --fields blocked_by,hold_kind,hold_reason 2>&1)
printf '%s\n' "$sslist" | head -4 | sed 's/^/    /'
printf '    ... %s lines total; "local main" note lines in the digest: %s (expect 0)\n' \
  "$(printf '%s\n' "$sslist" | wc -l | tr -d ' ')" "$(printf '%s\n' "$sslist" | grep -c 'local main' || true)"

# ---------------------------------------------------------------- part 2 ------
hr "PART 2 - Done outcomes survive worker cleanup as bounded, prose-free memory"

H="$EV/fleet-home"; rm -rf "$H"
mkdir -p "$H/data/scout-beta" "$H/state" "$H/config" "$H/projects/alpha-worktree" "$H/fakebin"
cp "$R/.tasks.toml" "$H/.tasks.toml"
printf '## In flight\n\n## Queued\n\n## Done\n' > "$H/data/backlog.md"
cat > "$H/fakebin/tmux" <<'SH'
#!/usr/bin/env bash
set -u
case "${1:-}" in
  list-windows) sed -n 's/^window=[^:]*://p' "${FM_HOME:?}"/state/*.meta 2>/dev/null ;;
  display-message) case "$*" in *pane_current_command*) printf 'codex\n' ;; *) printf '%%1\n' ;; esac ;;
  capture-pane) printf 'running tests\nesc to interrupt\n' ;;
esac
exit 0
SH
chmod +x "$H/fakebin/tmux"
for b in treehouse no-mistakes gh gh-axi; do printf '#!/usr/bin/env bash\nexit 0\n' > "$H/fakebin/$b"; chmod +x "$H/fakebin/$b"; done
FB="$H/fakebin"

cd "$H"
tasks-axi add ship-alpha  "Fix alpha login redirect"  --kind ship  --repo alpha --start >/dev/null
tasks-axi add scout-beta  "Investigate beta latency"  --kind scout --repo beta  --start >/dev/null
tasks-axi add local-gamma "Tune gamma cache"          --kind ship  --repo gamma --start >/dev/null
tasks-axi add queued-delta "Refactor delta parser"    --kind ship  --repo delta >/dev/null
tasks-axi done ship-alpha  --pr https://github.com/kunchenguid/firstmate/pull/1207 >/dev/null
tasks-axi done scout-beta  --report data/scout-beta/report.md >/dev/null
tasks-axi done local-gamma --note "local main" >/dev/null

python3 - "$H/data/backlog.md" <<'PY'
import sys
p = sys.argv[1]; s = open(p).read()
s = s.replace(
  "- [ ] queued-delta - Refactor delta parser (repo: delta) (kind: ship) (since 2026-07-28)\n",
  "- [ ] queued-delta - Refactor delta parser (repo: delta) (kind: ship) (since 2026-07-28)\n"
  "  QUEUED BODY: needs the captain to pick the parser strategy first\n")
s = s.replace(
  "- [x] scout-beta - Investigate beta latency data/scout-beta/report.md (repo: beta) (kind: scout) (reported 2026-07-28)\n",
  "- [x] scout-beta - Investigate beta latency data/scout-beta/report.md (repo: beta) (kind: scout) (reported 2026-07-28)\n"
  "  DECISION BODY: captain chose route north over route south, do not publish\n"
  "  report says the beta p99 regression came from the internal billing shim\n")
s = s.replace(
  "  local main\n",
  "  local main\n"
  "  TASK NOTE: credentials rotated by hand, see the private runbook\n"
  "  /Users/yelen/private/local-gamma-worktree\n")
s = s.rstrip("\n") + "\n"
s += "  STATUS PROSE: blocked on captain, then unblocked after standup /Users/yelen/private/stranded\n"
s += "hand written done line, kept for history /Users/yelen/private/free-form\n"
open(p, "w").write(s)
PY

echo "data/backlog.md on disk (the captain's real file, private prose and all):"
sed 's/^/    /' "$H/data/backlog.md"

printf 'window=firstmate:fm-ship-alpha\nworktree=%s/projects/alpha-worktree\nproject=%s/projects/alpha\nharness=codex\nkind=ship\nmode=no-mistakes\npr=https://github.com/kunchenguid/firstmate/pull/1207\n' "$H" "$H" > "$H/state/ship-alpha.meta"
printf 'working: rerunning the gate after review fixes\n' > "$H/state/ship-alpha.status"
printf '# Beta latency\n\nThe internal billing shim is the p99 source.\n' > "$H/data/scout-beta/report.md"

hr "PART 2a - worker still live (meta + status log + tmux endpoint present)"
ls "$H/state" | sed 's/^/    state\//'
echo
PATH="$FB:$PATH" FM_HOME="$H" "$R/bin/fm-fleet-view.sh"

hr "PART 2b - finished worker cleaned up: meta, status log and endpoint all gone"
rm -f "$H/state/ship-alpha.meta" "$H/state/ship-alpha.status"
rm -rf "$H/projects/alpha-worktree"
echo "    state/ is now empty; the tmux window no longer exists"
echo
PATH="$FB:$PATH" FM_HOME="$H" "$R/bin/fm-fleet-view.sh"

hr "PART 2c - the retained Done shape in the structured snapshot contract"
PATH="$FB:$PATH" FM_HOME="$H" "$R/bin/fm-fleet-snapshot.sh" --json \
  | jq '[.backlog.records[] | select(.state=="done")
        | {id, title, repo, kind, completion,
           artifact:(.pr_url // .report_path // .local_note),
           raw, body_lines, body_excerpt}]'

hr "PART 2d - In flight / Queued bodies are NOT over-redacted"
PATH="$FB:$PATH" FM_HOME="$H" "$R/bin/fm-fleet-snapshot.sh" --json \
  | jq -r '.backlog.records[] | "    \(.state)\t\(.id // "-")\tbody_excerpt=\(.body_excerpt | tostring)"'
echo
echo "    fm-bearings-snapshot.sh --fields bodies (queued only, Done rows carry none):"
PATH="$FB:$PATH" FM_HOME="$H" FM_BEARINGS_NOW=2026-07-28T12:00:00Z \
  "$R/bin/fm-bearings-snapshot.sh" --json --fields bodies | jq -c '.bodies' | sed 's/^/    /'

# ---------------------------------------------------------------- part 3 ------
hr "PART 3 - leak check on every Done-carrying consumer surface"

check() {  # <bindir> <label> <cmd...>
  local bindir=$1 label=$2; shift 2
  local out hits rc
  set +e
  out=$(PATH="$FB:$PATH" FM_HOME="$H" FM_BEARINGS_NOW=2026-07-28T12:00:00Z bash -c "$bindir/$*" 2>&1); rc=$?
  set -e
  hits=$(printf '%s' "$out" | grep -oE "$SECRETS" | sort -u | tr '\n' ' ')
  if [ -n "$hits" ]; then printf '  LEAK   %-52s rc=%s -> %s\n' "$label" "$rc" "$hits"
  else printf '  clean  %-52s rc=%s\n' "$label" "$rc"; fi
}

BASE="$EV/.base-scratch"; rm -rf "$BASE"; mkdir -p "$BASE"
(cd "$R" && git archive 514f0ab bin) | tar -x -C "$BASE"

echo "BASE commit 514f0ab (before the change), same home:"
check "$BASE/bin" "fm-fleet-snapshot.sh --json"          "fm-fleet-snapshot.sh --json"
check "$BASE/bin" "fm-fleet-view.sh"                     "fm-fleet-view.sh"
check "$BASE/bin" "fm-bearings-snapshot.sh --fields bodies" "fm-bearings-snapshot.sh --fields bodies"
echo
echo "TARGET commit 88327f8 (this change), same home:"
check "$R/bin" "fm-fleet-snapshot.sh --json"                          "fm-fleet-snapshot.sh --json"
check "$R/bin" "fm-fleet-view.sh"                                     "fm-fleet-view.sh"
check "$R/bin" "fm-bearings-snapshot.sh"                              "fm-bearings-snapshot.sh"
check "$R/bin" "fm-bearings-snapshot.sh --json"                       "fm-bearings-snapshot.sh --json"
check "$R/bin" "fm-bearings-snapshot.sh --fields bodies,paths,actions,endpoints" \
                                                                      "fm-bearings-snapshot.sh --fields bodies,paths,actions,endpoints"
rm -rf "$BASE"

# ---------------------------------------------------------------- part 4 ------
hr "PART 4 - the number lives in exactly two places"
(cd "$R" && grep -rn "done_keep" --include='*.md' --include='*.toml' --include='*.sh' . | grep -v '^./tests/') | sed 's/^/    /'
echo
echo "    fm-teardown.sh manual-backend prompt no longer hardcodes a count:"
(cd "$R" && grep -n 'keep Done to the' bin/fm-teardown.sh) | sed 's/^/    /'
echo
echo "    Deck / archive coupling:"
printf '    data/done-archive.md referenced only by: %s\n' \
  "$( (cd "$R" && grep -rl 'done-archive' --include='*.sh' --include='*.md' --include='*.toml' . | grep -v '^./tests/') | tr '\n' ' ')"
Evidence: Persisted backlog after 51 completions (50 retained, newest-first)
## In flight

## Queued
## Done
- [x] recent-51 - Recent outcome 51 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-50 - Recent outcome 50 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-49 - Recent outcome 49 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-48 - Recent outcome 48 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-47 - Recent outcome 47 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-46 - Recent outcome 46 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-45 - Recent outcome 45 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-44 - Recent outcome 44 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-43 - Recent outcome 43 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-42 - Recent outcome 42 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-41 - Recent outcome 41 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-40 - Recent outcome 40 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-39 - Recent outcome 39 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-38 - Recent outcome 38 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-37 - Recent outcome 37 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-36 - Recent outcome 36 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-35 - Recent outcome 35 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-34 - Recent outcome 34 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-33 - Recent outcome 33 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-32 - Recent outcome 32 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-31 - Recent outcome 31 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-30 - Recent outcome 30 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-29 - Recent outcome 29 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-28 - Recent outcome 28 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-27 - Recent outcome 27 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-26 - Recent outcome 26 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-25 - Recent outcome 25 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-24 - Recent outcome 24 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-23 - Recent outcome 23 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-22 - Recent outcome 22 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-21 - Recent outcome 21 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-20 - Recent outcome 20 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-19 - Recent outcome 19 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-18 - Recent outcome 18 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-17 - Recent outcome 17 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-16 - Recent outcome 16 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-15 - Recent outcome 15 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-14 - Recent outcome 14 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-13 - Recent outcome 13 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-12 - Recent outcome 12 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-11 - Recent outcome 11 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-10 - Recent outcome 10 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-09 - Recent outcome 9 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-08 - Recent outcome 8 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-07 - Recent outcome 7 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-06 - Recent outcome 6 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-05 - Recent outcome 5 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-04 - Recent outcome 4 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-03 - Recent outcome 3 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
- [x] recent-02 - Recent outcome 2 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
Evidence: Persisted durable archive (oldest item only)

## Archived 2026-07-28
- [x] recent-01 - Recent outcome 1 (repo: sample) (kind: ship) (done 2026-07-28)
  local main
Evidence: Fleet View after the finished worker is fully cleaned up - Done memory survives
# Fleet View

Schema: fm-fleet-snapshot.v1

## Under Way
No live task metadata found.

## Queued
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| queued-delta | Refactor delta parser | delta | ship | - | - |

## Done
| ID | Title | Repo | Kind | Blocked By | Artifact |
| --- | --- | --- | --- | --- | --- |
| local-gamma | Tune gamma cache | gamma | ship | - | local main |
| scout-beta | Investigate beta latency | beta | scout | - | data/scout-beta/report.md |
| ship-alpha | Fix alpha login redirect | alpha | ship | - | https://github.com/kunchenguid/firstmate/pull/1207 |
| - | (unstructured done line) | - | - | - | - |

(state/ is empty: meta, status log and tmux endpoint were all removed before this render)
Evidence: Base-vs-target leak check on the identical home
BASE commit 514f0ab (before the change), same home:
LEAK fm-fleet-snapshot.sh --json -> /Users/yelen/private DECISION BODY STATUS PROSE TASK NOTE billing shim credentials rotated hand written done line private runbook route north
LEAK fm-fleet-view.sh -> /Users/yelen/private hand written done line
LEAK fm-bearings-snapshot.sh --fields bodies -> /Users/yelen/private DECISION BODY STATUS PROSE TASK NOTE billing shim credentials rotated private runbook route north

TARGET commit 88327f8 (this change), same home:
clean fm-fleet-snapshot.sh --json
clean fm-fleet-view.sh
clean fm-bearings-snapshot.sh
clean fm-bearings-snapshot.sh --json
clean fm-bearings-snapshot.sh --fields bodies,paths,actions,endpoints

Queued bodies still revealed (not over-redacted):
[{"id":"queued-delta","body":"QUEUED BODY: needs the captain to pick the parser strategy first"}]
Evidence: tasks-axi pruning at 51 Done items in an isolated temporary home
tracked .tasks.toml in effect:
backend = "markdown"
[markdown]
path = "data/backlog.md"
archive = "data/done-archive.md"
done_keep = 50

backlog.md Done rows : 50 (expect 50)
done-archive.md rows : 1 (expect 1)
recent-01 still in backlog : 0 (expect 0)
recent-02 pushed to archive: 0 (expect 0)

archived (oldest only):
- [x] recent-01 - Recent outcome 1 (repo: sample) (kind: ship) (done 2026-07-28)

archive format preserved:
## Archived 2026-07-28
- [x] recent-01 - Recent outcome 1 (repo: sample) (kind: ship) (done 2026-07-28)
local main

retained window, Done chronology newest-first:
- [x] recent-51 ...
- [x] recent-50 ...
...
- [x] recent-03 ...
- [x] recent-02 ...

session-start digest bound unchanged (tasks-axi list --limit 80, the exact call fm-session-start.sh makes):
count: 50 ... 55 lines total; "local main" note lines in the digest: 0 (expect 0)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed ✅
  • ⚠️ bin/fm-fleet-snapshot.sh:381 - The new Done body suppression only covers indented lines that attached to a structured row. Verified by running the changed script: with the Done section - [x] good - Structured done (repo: r) (kind: ship) (done 2026-07-01) / local main / PRIVATE BODY /Users/x/secret / Free-form done note about /Users/x/other-secret / INDENTED PRIVATE PROSE /Users/x/third-secret, the structured row is suppressed correctly (body_lines [], body_excerpt null, local_note lifted to "local main", private line absent). But the free-form line breaks body attachment at line 372 (.records[-1].structured == true is false), so the following indented line falls through to line 378 and is emitted as {&#34;structured&#34;:false,&#34;raw&#34;:&#34; INDENTED PRIVATE PROSE /Users/x/third-secret&#34;}, and bin/fm-fleet-view.sh:60 (dash($r.title // $r.raw)) renders both absolute paths in the Done table. This contradicts the new header claim at bin/fm-fleet-snapshot.sh:29-30 that the suppression keeps "absolute local paths written under a Done item out of every consumer", and it is reachable under the supported config/backlog-backend=manual mode where data/backlog.md is hand-edited. Fix would be to drop or redact raw for unstructured Done records (or attach stray indented lines in the Done section to the preceding record regardless of structure), but that changes user-visible fm-fleet-view output, so it needs the author's decision.
  • ℹ️ .tasks.toml:6 - Raising done_keep from 10 to 50 grows the unbounded fm-fleet-snapshot.sh --json payload, which .agents/skills/bearings/SKILL.md:25 names as the fallback when fm-bearings-snapshot.sh is unavailable. Measured on a home with 50 Done rows and no live tasks: 52568 bytes, since each Done record carries ~25 fields plus raw. The primary surfaces stay bounded - bearings emitted 1820 bytes with landed capped at 6 via FM_BEARINGS_LANDED_PER_HOME, and the session-start digest still caps at 80 items with tasks-axi list ordering in_flight then queued then done so Done truncates last. Noting the tradeoff on the documented fallback path; no change required.

🔧 Fix: redact unstructured Done prose from fleet snapshot
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-decision-hold-lifecycle.test.sh (includes the new test_tasks_axi_default_done_keep_archives_oldest_at_51)
  • bash tests/fm-fleet-snapshot-view.test.sh (includes the new test_done_recent_history_survives_worker_cleanup_without_private_prose)
  • bash tests/fm-teardown.test.sh (updated done_keep prompt assertions, compatible + manual-backend opt-out)
  • bash tests/fm-bearings-snapshot.test.sh (existing snapshot consumers after the --fields bodies narrowing)
  • bash tests/fm-session-start.test.sh (session-start digest size bounds preserved)
  • bash tests/fm-backlog-handoff.test.sh (secondmate isolation and Done-record refusal preserved)
  • bash tests/fm-brief.test.sh (crewmate scaffolding regression check)
  • Manual E2E: REPO=&lt;worktree&gt; bash /var/folders/9d/8w50jhgd79x63rgbq_5cyyvm0000gn/T/no-mistakes-evidence/01KYN1F5W2FP0A3RE9T5Q88DBB/run-done-memory-e2e.sh
  • Manual E2E part 1: isolated temp home + tracked .tasks.toml, 51 x (tasks-axi add / tasks-axi done --note &#34;local main&#34;), asserted 50 retained / 1 archived / recent-01 gone from backlog / recent-02 absent from archive / ## Archived format / newest-first chronology
  • Manual E2E part 1: tasks-axi list --file &lt;backlog&gt; --limit 80 --fields blocked_by,hold_kind,hold_reason - the exact call bin/fm-session-start.sh makes - over the 50-row Done window, confirming bodies stay out of the startup digest
  • Manual E2E part 2: FM_HOME=&lt;home&gt; bin/fm-fleet-view.sh before and after deleting state/&lt;id&gt;.meta, state/&lt;id&gt;.status and the tmux endpoint
  • Manual E2E part 2: FM_HOME=&lt;home&gt; bin/fm-fleet-snapshot.sh --json | jq on Done records for id/title/repo/kind/completion/artifact plus body_lines == [] and body_excerpt == null
  • Manual E2E part 2d: bin/fm-bearings-snapshot.sh --json --fields bodies confirming queued bodies still revealed and Done rows carry none
  • Manual E2E part 3: leak-check of fm-fleet-snapshot.sh --json, fm-fleet-view.sh, fm-bearings-snapshot.sh (default, --json, --fields bodies,paths,actions,endpoints) against the same home on base 514f0ab (via git archive) vs target 88327f8
  • Manual E2E part 4: grep -rn done_keep --include=&#39;*.md&#39; --include=&#39;*.toml&#39; --include=&#39;*.sh&#39; and grep -rl done-archive to confirm the number is not duplicated and Deck is not coupled to the archive
⚠️ **Document** - 1 info
  • ℹ️ docs/configuration.md:36 - docs/configuration.md hand-copies the done_keep = 50 value from .tasks.toml, and nothing guards the two against drift. The placement policy asks for schema-backed facts to be generated or drift-checked rather than hand-copied, and this change just exercised that exact path (the number moved and the prose had to be updated by hand). A follow-up could add a cheap assertion - e.g. in tests/fm-instruction-owners.test.sh, which already pins the backlog-mechanics owner pointer, or in bin/fm-doc-audience-check.sh - that the value in docs/configuration.md matches the tracked .tasks.toml. Implementing it means editing a test or check script, which this documentation phase must not do.

🔧 Fix: point docs at .tasks.toml for done_keep value
2 infos still open:

  • ℹ️ docs/configuration.md:36 - You chose to fix the done_keep drift finding, whose proposed remedy was a test assertion pinning docs/configuration.md to .tasks.toml. This phase may not edit tests or check scripts, so I resolved it structurally instead: the doc no longer states done_keep = 50 at all, it names the done_keep setting and declares the tracked .tasks.toml the single owner of its value. Drift is now impossible rather than guarded, and this matches the convention the branch already established in bin/fm-teardown.sh and AGENTS.md lines 318/439 ("the configured recent Done history"), plus AGENTS.md:438 which already names .tasks.toml a co-owner of backlog retention. Flagging it because it is a judgment call that removes a value operators previously read directly from this reference page: the surrounding section still lists literal config values elsewhere (for example .no-mistakes.yaml pinning commands.lint, and the env-var default block). If you would rather the page keep stating the current number, revert this one line and add the drift assertion in a follow-up that is allowed to touch tests/fm-instruction-owners.test.sh.
  • ℹ️ docs/decision-hold-lifecycle.md:55 - The branch added one test case to tests/fm-decision-hold-lifecycle.test.sh (Done retention at 51) and one to tests/fm-fleet-snapshot-view.test.sh (Done body suppression), so both scripts now print one more ok - line than the transcript recorded here. I deliberately did not regenerate that block: it is a date-pinned archived evidence record (2026-07-14 / 07-17 / 07-22) scoped to the decision-hold guarantee, and it is already a curated subset - I ran tests/fm-fleet-snapshot-view.test.sh and it emits 16 ok lines while the doc lists the 3 decision-hold-relevant ones. Both new cases prove Done retention and snapshot redaction, not decision holds, so omitting them follows the same curation rule. If you instead treat these blocks as verbatim command output that must be refreshed on every colocated test addition, this record needs a re-run and update.

🔧 Fix: pin documented done_keep value to .tasks.toml
1 info still open:

  • ℹ️ tests/fm-instruction-owners.test.sh:239 - This phase's rules restrict it to documentation files and doc comments and explicitly forbid changing tests. The captain's finding done-keep-drift-assertion-still-missing directed the opposite: restore the documented value in docs/configuration.md and add the drift regression in the test/check owner, explicitly rejecting a doc-only pointer resolution. I followed the captain's directive, so this document-phase commit touches tests/fm-instruction-owners.test.sh in addition to docs/configuration.md. The test change is purely additive and assertion-only (no product code, no behavior change): it reads done_keep from the tracked .tasks.toml and asserts docs/configuration.md quotes the same number. Flagging so the outer executor knows the phase commit is not doc-only and can route it accordingly if a later gate expects a documentation-only diff.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@yelenplays
yelenplays force-pushed the fm/firstmate-deck-completion-memory branch from d59055c to cf4cefb Compare July 30, 2026 02:26
@yelenplays yelenplays closed this Jul 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant