# Talking Stick 0.1.3

Date: 2026-04-28

Patch release covering one stale-state cleanup tool and one bugfix that
prevents the duplicate-member problem the cleanup tool addresses.

## Added

### `tt kick` / `kick_member`

Rooms occasionally accumulate ghost members — agents that registered, then
disappeared without leaving cleanly. Until now there was no first-class way to
evict them. `tt kick <agent_id>` (CLI) and the `kick_member` MCP tool fill that
gap.

```bash
tt kick codex:6e030b4c                 # only succeeds if target is gone
tt kick codex:6e030b4c --force         # force-remove a still-active member
tt kick codex:6e030b4c --reason "stale guardian"
```

Default behavior:

- caller must be an active member of the room
- target must not be the caller (use `tt leave` for self-removal)
- target must be detected `gone` past the existing silence-grace window
  (`2 * heartbeatIntervalMs`); otherwise the call rejects with `target_active`

`--force` / `force: true` bypasses the idleness check for cases where liveness
detection is wrong (PID reuse, suspended processes) or the operator explicitly
wants to remove a still-running member.

State transitions mirror `leave_room`: if the target was the owner, ownership
and lease state are cleared; if the target was the reservation, the reservation
is cleared; if no active members remain, the room is deleted. Each successful
kick records a `kick` room event with `from_agent_id`, `to_agent_id`, and
optional `reason` so other agents tailing `get_room_events` see the cleanup.

New `ProtocolErrorCode`s: `unknown_target`, `target_active`, `cannot_kick_self`.

The bundled skill points agents at `kick_member` for cleaning up `inactive`
ghost members visible in `tt state`, with `force: true` reserved for explicit
operator instruction.

## Fixed

### Stable codex agent ids across MCP and shelled-out CLI

Codex sometimes runs `tt` directly as an MCP subprocess and sometimes shells
out via Bash to invoke the `tt` CLI. Both paths derive the same `codex:<hash>`
agent id when codex exposes `CODEX_THREAD_ID`, but when only
`CODEX_MANAGED_BY_NPM=1` is set the previous logic anchored session id on the
immediate parent pid:

- MCP subprocess: parent = codex root → stable
- shelled-out CLI: parent = bash subshell → fresh pid every invocation

The result was that long codex sessions accumulated multiple `codex:<hash>`
members in a single room (one per shell-out), each registering as a new
`human_guardian` and lingering until liveness expired them.

`resolveHarnessSessionId` now walks process ancestry when the harness env
signal exists but no explicit session id is exposed, and uses the deepest
matching ancestor's `pid+startTime` as the anchor. The harness root is stable
across MCP subprocess and shell-out CLI invocations, so they collapse to one
agent id.

This is a CLI/MCP-time fix; it does not retroactively merge duplicate members
already recorded in a room. Use `tt kick` (above) for that.

## Verification

- `npm run typecheck`
- `npm test` — 222 tests across 14 files
- `npm run build`
- `git diff --check`
- `npm pack --dry-run --ignore-scripts`
