trusty-mpm 1.7.0

trusty-mpm: unified multi-agent orchestration platform (core, daemon, CLI, TUI, Telegram)
Documentation
---
name: tm-session-pause
description: Pause the current PM session — snapshot todos, git state, and context to a project-local session file, prune stale worktrees, and print the resume path
user-invocable: true
version: "1.0.0"
category: pm-workflow
tags: [session, pause, worktree, context, pm-recommended]
effort: medium
---

# /tm-session-pause — Pause & Snapshot

Focused entry point for pausing the current session and saving all work state
for a later `/tm-session-resume`. This is the *action*; the policy, thresholds,
and full format reference live in `/tm-session-management`.

## What This Does

When invoked, this skill:
1. Captures current work state — todos (`TodoWrite`), git status, and a short
   context summary (plus any message you pass after the command).
2. Writes a project-local snapshot at
   `.trusty-mpm/sessions/session-{timestamp}.md`.
3. **Appends** a `pause` line to the append-only
   `.trusty-mpm/sessions/sessions-log.jsonl` (never overwrites — so concurrent
   `tm` sessions in the same project don't clobber each other's resume target).
4. **Prunes stale git worktrees** left behind by decommissioned managed
   sessions (see below).
5. **Publishes the snapshot** — ONLY where the project tracks
   `.trusty-mpm/sessions/`, by committing it on a fresh `chore/sessions-*`
   branch off the project's default branch, pushing, and opening an
   auto-merging docs-only PR (#7282). A project that gitignores the store
   publishes nothing, and no snapshot is committed by hand to make up for it.
6. Prints the snapshot path, plus the PR URL when one was opened.

## Usage

```
/tm-session-pause [optional message describing current work]
```

Examples:
```
/tm-session-pause
/tm-session-pause Working on the auth refactor, about to test the login flow
/tm-session-pause Need to context-switch to an urgent bug fix
```

## Session File Location (Project-Local)

Snapshots are stored **project-local**, never under the user's home directory,
so pausing in project A and opening project B never loads project A's state:

```
<project-root>/.trusty-mpm/sessions/
├── sessions-log.jsonl              # append-only per-session pause/resume log
├── <session-id>/
│   └── session-YYYYMMDD-HHMMSS.md  # human-readable snapshot
└── session-YYYYMMDD-HHMMSS.md      # pre-#5272 snapshot, still resolvable
```

`sessions-log.jsonl` holds one JSON object per line — one per pause/resume
event. `snapshot` is the path relative to `sessions/`:

```json
{"session_id":"<id>","event":"pause","snapshot":"<id>/session-20260715-101500.md","timestamp":"2026-07-15T10:15:00Z"}
```

Because it is **append-only**, two `tm` sessions pausing in the same project
each keep their own history, and the log is what says which snapshot is whose.
The legacy global `LATEST-SESSION.txt` pointer is **no longer written** — a
single overwritten pointer let concurrent sessions clobber each other's resume
target.

🔴 **Resume resolves only your own session's snapshots (#5272).** The
latest-overall, `LATEST-SESSION.txt`, and mtime fallbacks are gone: with the PM
on the project's main checkout, several sessions share this store, and each of
those steps turns "no snapshot for me" into "someone else's snapshot". A flat
pre-#5272 file at the store root still resolves through its log line; one with
no log line resolves for nobody.

## How the Snapshot Reaches the Default Branch (#7282)

In a project that TRACKS `.trusty-mpm/sessions/`, the pause publishes the
snapshot instead of leaving it on disk. The tool commits the snapshot file and
`sessions-log.jsonl` — and nothing else — onto a fresh
`chore/sessions-<session>-<timestamp>` branch cut from the project's default
branch, pushes it, and opens a docs-only PR through `tm pr open` with squash
auto-merge armed. The result carries `snapshot_publish` with the branch, commit,
and PR URL; report the URL alongside `snapshot_path`.

The default branch is the one `config.yaml`'s `projects:` entry declares, else
whatever `origin/HEAD` points at, else `main` — so a `master` or `develop`
project publishes from its own branch rather than being told every pause is
illegal.

The commit is built with git plumbing against a scratch index, so your branch,
your HEAD, and every other file in the checkout are untouched — a pause can
never sweep in another session's dirty file, and it never runs `git add`,
`git stash`, or `git checkout`.

Two refusals, both errors rather than warnings, and the snapshot file is written
either way:

- **Not on the default branch.** Publishing from a feature branch would put
  unrelated commits in the PR, so the tool refuses and names the branch it
  expected. Commit and open the PR by hand.
- **A step failed.** The error names the step. Once the commit exists it also
  names the branch it is on, so push that branch rather than re-running the
  pause and building a second commit.

Once the PR lands, `git pull --ff-only` in the main checkout brings the snapshot
back locally — see `/tm-workflow`, "Keep the main checkout fresh".

In a project that keeps `.trusty-mpm/sessions/` in `.gitignore`, session state is
machine-local: no commit, branch, or PR is created. Every no-PR outcome reports
`snapshot_publish.status: "skipped"` with a `reason` saying which — `not_tracked`
(git-ignored sessions), `not_a_git_repo` (the project directory is not a
checkout), or `unchanged` (the snapshot already matches the default branch).

## Snapshot Content

```markdown
# Session Pause - {timestamp}

## Summary
{user-provided message, or auto-generated from current todos}

## Completed
{completed todos/tasks this session}

## In Progress
{in-progress todos with detailed state, including each dispatched agent's id}

## Next Steps
{pending todos and recommended next actions}

## Git Context
Branch: {current branch}
Last commit: {hash and message}
Uncommitted changes: {git status summary}

## Tmux Window
{session_name:window_index:window_id, e.g. main:2:@7 — omit this section entirely when not inside tmux}
```

The `## Tmux Window` section records the originating tmux window so
`/tm-session-resume` can re-align you to it with `tmux select-window`. Only
include the section when the capture step below produced a value; snapshots
created outside tmux simply omit it (resume treats absence as a no-op).

**A dispatched agent's survival across a relaunch is conditional, not
absolute** — three agents survived a relaunch on 2026-09-14, so do not write
an in-progress entry assuming every in-flight dispatch is dead. Record each
agent's id, taken from its dispatch result, in that agent's `## In Progress`
entry. A 2026-09-14 pause snapshot with no recorded id mislabeled agent
`a2615654d96bcb4a9` as `ticketing` when it was actually the B1 `code-critic`.

## Procedure

Reconcile the todo list against `git status` first — mark blockers explicitly —
so the snapshot reflects the *current* state, not a stale one. Then capture
the tmux window (this ONE step stays PM-side bash — only the PM's own shell
has tmux client access; the MCP tool never touches tmux):

```bash
# Capture the current tmux window ONLY when inside tmux; omit the field otherwise.
if [ -n "$TMUX" ]; then
  tmux display-message -p '#{session_name}:#{window_index}:#{window_id}'
fi
```

Then **load the tool schema before calling it** — see `/tm-session-management`
("MCP Session-Tool Failure Diagnosis") for why absence from your loaded tool
list does not mean the tool is unavailable:

```
ToolSearch(query: "select:mcp__trusty-mpm__session_context_pause")
```

This is mandatory before attempting the call and before considering any
fallback (see "If the Tool Call Fails" below) — do not skip straight to a
hand-written snapshot just because the tool isn't in your loaded list yet.

Then call the MCP tool to write the snapshot, append the pause-log entry, and
prune worktrees, all in one step:

```
mcp__trusty-mpm__session_context_pause(
  project_dir: <absolute path to the current project root>,
  # session_id: OMIT IT. The tool derives it — see below.
  summary: <human-readable current-state prose>,
  completed: [<completed todos/tasks this session>, ...],   # optional
  in_progress: [<in-progress todos with detailed state>, ...],  # optional
  next_steps: [<pending todos and recommended next actions>, ...],  # optional
  tmux_window: <the string captured above, when inside tmux>,   # optional
  prune_worktrees: true   # default; set false to skip the prune step
)
```

**Do not pass `session_id`, and never invent one.** The resume lookup is exact
string equality, so an id you make up here has to be retyped identically after a
restart or the next `/tm-session-resume` finds nothing — which is what used to
happen on nearly every resume (#6888). Omit the argument and the tool derives the
id from what you actually are: your managed session id when you run in a `tm`
pane, else your tmux window id. `session_context_catchup` derives the same id the
same way, so the two agree without you doing anything. Pass `session_id`
explicitly ONLY to attribute the pause to a specific id you already know — never
to supply a "stable-looking" string of your own.

The tool returns
`{ session_id, snapshot_path, timestamp, pruned_worktrees, skipped_dirty_worktrees, snapshot_publish, ref_name, ref_published, ref_error }`,
where `session_id` is the id it filed the snapshot under and `snapshot_publish`
names the branch, commit and PR the snapshot was published as (see "How the
Snapshot Reaches the Default Branch" above). It writes
`.trusty-mpm/sessions/<session-id>/session-YYYYMMDD-HHMMSS.md` in the same section format
`/tm-session-resume` already parses (`## Summary` / `## Completed` /
`## In Progress` / `## Next Steps` / `## Git Context` / `## Tmux Window`,
each omitted when empty), appends the matching `pause` line to the
append-only `sessions-log.jsonl` (never overwrites — concurrent `tm` sessions
in the same project keep their own history), and computes the
`## Git Context` section (branch, last commit, uncommitted-changes summary)
itself — no separate `git status`/`git log` shell-out needed.

Report the returned `snapshot_path` to the user.

### The Session Ref (ADR-0062, #7830)

Every pause — on a project that tracks `.trusty-mpm/sessions/` or not — also
appends the snapshot to this session's own append-only git ref,
`refs/tm/sessions/<user-id>/<session-key>`, and lease-pushes it to `origin`.
That ref is what a resume from a fresh clone rebuilds its cache from.

- **`ref_published: false` with a non-null `ref_error` MUST be reported.** The
  local snapshot is the primary write and is already on disk, so the pause
  succeeded — but the durable copy did not reach `origin` and nothing else will
  say so. Report the `ref_error` text verbatim; do not retry it yourself.
- `ref_name` names the ref the pause targeted, even when the push failed.
- With `[session_refs] enabled = false` in `~/.trusty-mpm/config.toml` no ref is
  written at all: `ref_name` and `ref_error` are both `null` and
  `ref_published` is `false`.

**Operator-visible:** a pause PUSHES your summary, in-progress items and next
steps to whatever `origin` the project has — a company remote, a fork, a
third-party host. The only gate before that push is a prefix-based credential
scan (known provider key shapes such as `ghp_`, `sk-ant-`, `AKIA`, and PEM
private-key headers); it is not a general secret detector and will not catch a
password, a customer name, or an internal URL you typed into the summary. Keep
the pause text to what you would put in a PR description, or set
`[session_refs] enabled = false`. Anyone with push access to `origin` can also
write a session ref, by the same permission that lets them push a branch —
ADR-0062 decision 10; a repository whose push set is not trusted should turn
this off.

**If `skipped_dirty_worktrees` is non-empty, you MUST report it** — do not let
it disappear into the tool result. Each entry is
`{ path, reason, dirty_files, unpushed_commits }` and names a worktree the
prune refused to delete because it still holds uncommitted or unpushed work.
List them for the user, with the path and reason, and say plainly that the
work is still on disk and needs a human decision (commit it, push it, or
delete the directory deliberately). Never summarise them as "some worktrees
were skipped" — the whole point of the #4091 guard is that unsaved work stops
being invisible.

## If the Tool Call Fails — Diagnose Before Falling Back

Never report a bare "MCP pause tool unavailable." Follow the shared diagnosis
procedure in `/tm-session-management` ("MCP Session-Tool Failure Diagnosis")
— it is not optional, and no step in it may be skipped or reordered. Only
once that procedure has established that no `mcp__trusty-mpm__*` tool is
available, hand-write the snapshot as a fallback — and label it as one (see
"Self-Identifying Fallback Snapshot" below). This is the only point at which
writing a file by hand is permitted.

When you do fall back, match the tool's own format exactly, not free prose —
reuse the shape from "Snapshot Content" above verbatim: RFC3339
timestamps (`date -u +%Y-%m-%dT%H:%M:%S.%6N+00:00` or equivalent), the exact
literal section headers with nothing appended after them on the same line
(`## Summary`, `## Next Steps`, etc. — never `## Next Steps (some
annotation)`, which corrupts what `/tm-session-resume`'s parser extracts, see
`extract_section` hardening in `session_finder.rs`), and the three-line `##
Git Context` shape (`Branch: … / Last commit: … / Uncommitted changes: …`).

### Self-Identifying Fallback Snapshot

A hand-written snapshot must never be indistinguishable from a tool-written
one — the only way this project's own history could tell them apart was a
forensic timestamp tell (real tool output carries fractional seconds and a
`+00:00` offset; hand-written snapshots drifted to a bare `Z` suffix), which
is how six drifted files went unnoticed for three days. Instead, mark it
explicitly. Add a line immediately under the `# Session Pause - {timestamp}`
title, before `## Summary`:

```markdown
# Session Pause - {timestamp}

> FALLBACK SNAPSHOT — hand-written, not produced by
> `mcp__trusty-mpm__session_context_pause`. Observed failure: {the exact
> error text from step 3 above, or "no mcp__trusty-mpm__* tools present in
> either the loaded or deferred tool lists" if that is what was actually
> observed}.

## Summary
...
```

This is additive prose above the parsed sections — `/tm-session-resume`'s
`extract_section` reads sections by header, so the marker line does not
interfere with parsing, but a human (or a future PM) resuming from it
immediately sees it was not tool-generated and why. State in your report to
the user, in the same terms, which step you stopped at and what you actually
observed — never the bare "MCP pause tool unavailable."

## Worktree Pruning

Decommissioned managed sessions (`tm session new` / `mcp__trusty-mpm__session_new`)
can leave orphaned per-session git worktree directories behind. By default,
`session_context_pause` prunes them as part of the call above (`prune_worktrees:
true`, the default) — the same in-process engine `tm session prune-worktrees`
uses. Only directories with **no** corresponding active session are ever
touched; the tool returns the list of paths removed as `pruned_worktrees`.

**A worktree holding unsaved work is never removed** (#4091). Before deleting
anything, the prune checks each candidate for uncommitted or staged changes,
untracked (non-ignored) files, and commits that exist on no remote — and
refuses to touch it if any are present, or if the check itself cannot
complete. Those are returned as `skipped_dirty_worktrees` (see above) rather
than deleted. `/tm-session-pause` has **no** option that can override this;
discarding unsaved work requires the deliberate CLI opt-in below.

Pass `prune_worktrees: false` to skip this step (e.g. to preview first with
the CLI):

```bash
tm session prune-worktrees          # dry-run by default (preview only)
tm session prune-worktrees --force  # remove the orphaned dirs, sparing dirty ones
```

`--force` still refuses to delete a worktree with unsaved work; it lists them
on stderr instead. Only `tm session prune-worktrees --force --discard-dirty`
destroys that work, and it should be reached for only after reading that list.

`tm doctor`'s `worktrees` probe reports the orphan count and suggests this
command; run it whenever that probe is non-zero, and always before ending a
long session that spawned managed sessions.

## Durable Tasks vs. Snapshot Prose

The snapshot captures the PM's own `TodoWrite` state as prose. For work items
that must survive as durable, queryable tasks visible to *any* future session
(not just a resumed one), promote them via `mcp__trusty-memory__task_add` /
`task_list` / `task_complete` before pausing.

## Token Budget

~5–10k tokens to execute (2–5% of context budget). The payoff is that all
remaining context is freed for a clean resume, a context switch, or a break.

## Resume Later

```
/tm-session-resume
```

or from the CLI:

```bash
tm session catchup
```

## Related

- `/tm-session-resume` — load context from a paused session
- `/tm-session-management` — policy, the 70% auto-pause threshold, full format
  reference, and the task-list integration this action is a focused slice of