refactor: extract tmux-launch-agent from implement-issue/setup-skills, overhaul PKM skills

- New common/misc/tmux-launch-agent skill with detect-agent script
  and agents-seed.md (moved from engineering/setup-skills)
- implement-issue simplified: delegates agent dispatch to
  tmux-launch-agent instead of inlining it
- setup-skills stripped of Section E (agent CLI config)
- New in-progress/agent-handoff skill using tmux-launch-agent
- PKM skills simplified: conversation-summary and pkm-curation
  rewritten with leaner process, crit streamlined
- knowledge-gardener removed (moved to common/in-progress/)
- All READMEs updated to match
This commit is contained in:
2026-07-06 11:29:16 -04:00
parent 1614a0d469
commit c92ee69440
13 changed files with 547 additions and 608 deletions
+57
View File
@@ -0,0 +1,57 @@
---
name: tmux-launch-agent
description: Fork a new agent CLI session into a new tmux window, detected from the current agent.
disable-model-invocation: true
---
## Agent CLI Seed Data
The agent config (binary, `args` convention per agent) and field meanings are in [`agents-seed.md`](agents-seed.md). Step 2 reads it to find the calling agent's entry.
## Process
1. **Detect the calling agent** — Run `./detect-agent` (sibling to this skill). If it exits 1 (agent unknown), report the failure and stop — the agent name is required.
Completion criterion: The agent name is known and non-empty.
2. **Look up the agent config** — Find the agent's entry in [`agents-seed.md`](agents-seed.md) by `name`. Extract its `binary` and `args` fields.
Completion criterion: The agent's entry is found and its `binary` and `args` are known.
3. **Parse flags** — Scan the user's arguments for optional flags (which must come before the prompt text):
- `--name <title>` or `-n <title>` — the tmux window title. Extract the title and remove the flag and its value from the arguments list.
- `-c <path>` — the working directory for the new window. Extract the path and remove the flag and its value from the arguments list.
The remaining text after stripping both flags is the prompt for the child agent.
If `--name`/`-n` is absent, tmux auto-names the window.
If `-c` is absent, the new window inherits the current pane's working directory.
Completion criterion: The arguments are split into an optional window name, an optional directory path, and the remaining prompt text.
4. **Build the inner command** — Combine the agent config with the remaining user-supplied prompt arguments. The `args` template determines how the prompt is delivered:
- **Prompt-file agents** (`args` contains `{prompt}`) — Write the prompt text to a temporary file under `/tmp/` and substitute the file path for `{prompt}` in the args template. For example, an agent with `args: "@{prompt}"` becomes `<binary> @/tmp/tmux-launch-XXXX.md`.
- **Stdin-pipe agents** (`args` is empty) — Pipe the prompt text via `echo` into the binary.
- **No prompt** — If the user passed no arguments (after removing flags), launch the binary bare (interactive start) with no prompt file or pipe.
Completion criterion: The inner command is correctly built per the target agent's `args` convention (prompt-file, stdin-pipe, or bare).
5. **Wrap with the environment runner** — Detect whether `mise` is available via `command -v mise`:
- **mise available** — Wrap the inner command as `mise x --allow-env='*' -- <inner-command>`.
- **mise absent** — Wrap the inner command as `$SHELL -c '<inner-command>'`.
Completion criterion: A valid shell command string is ready.
6. **Fork into a new tmux window** — Build and run:
```
tmux new-window <name-flag> <dir-flag> "<shell-command>"
```
- `<shell-command>` — the wrapped command from step 5.
- `<name-flag>` — `-n "<title>"` if a window name was parsed in step 3, omitted otherwise.
- `<dir-flag>` — `-c <path>` if a directory was parsed in step 3, omitted otherwise.
Completion criterion: `tmux new-window` exits 0 and a new tmux window appears with the agent CLI session active.
@@ -0,0 +1,44 @@
# TMUX Launch Agent — Agent CLI Seed Data
## Agents
```yaml
agents:
- name: pi
binary: pi
args: "@{prompt}"
description: "My primary agent harness. Accepts prompt file via {prompt}."
- name: opencode
binary: opencode
args: ""
description: "OpenCode agent. Pipes stdin via cat."
note: "Uses stdin piping: args must be empty, prompt via pipe."
- name: goose
binary: goose
args: ""
description: "Goose agent. Accepts prompt file via -i flag."
note: "Uses stdin piping: args must be empty, prompt via pipe."
- name: codex
binary: codex
args: "@{prompt}"
description: "OpenAI Codex. Accepts prompt file via {prompt}."
- name: claude
binary: claude
args: ""
description: "Anthropic Claude CLI. Pipes stdin via cat."
note: "Uses stdin piping: args must be empty, prompt via pipe."
```
## Field meanings
| Field | Description |
|-------|-------------|
| `name` | Display name used in menus and `--agent` flag |
| `binary` | Command name expected on PATH |
| `args` | Static arguments appended after the binary. May include `{prompt}` which is substituted at invocation time with the absolute path to the prompt file. Empty string means stdin piping (prompt is piped via `cat`). |
| `description` | Short human-readable description for the setup menu |
| `note` | Optional additional context |
+353
View File
@@ -0,0 +1,353 @@
#!/usr/bin/env bash
#
# detect-agent — Detect which AI agent CLI launched this script.
#
# Checks environment variables and walks the parent process tree to
# identify the originating coding agent. Designed for agent-agnostic
# tools (skills, scripts, hooks) that need to adjust behaviour based
# on which agent invoked them.
#
# Usage:
# detect-agent # print name (e.g. "pi", "opencode")
# detect-agent --name # same as default
# detect-agent --json # JSON: {"agent":"pi","detected":true}
# detect-agent --verbose # human-readable with debug info
# detect-agent --list-agents # list all known agent names
#
# Exit codes:
# 0 agent detected
# 1 unknown / not detected
#
# Supported agents (add your own at the top of each definition map):
# pi, opencode, aider, claude, codex, cursor, windsurf, continue,
# github-copilot, goose
#
# Detection order:
# 1. Environment variable signatures (most reliable)
# 2. Walk parent process tree skipping shells
# 3. Check /proc/$PPID/{comm,cmdline} as fallback
#
set -euo pipefail
########################################################################
# Agent definitions
########################################################################
# Each entry: "<env-var>:<match-value>:<agent-name>"
# If match-value is "*", any non-empty value matches.
# If match-value is a literal, the env var must equal it.
AGENT_ENV_SIGS=(
"PI_CODING_AGENT:true:pi"
"OPENCODE_CONFIG:*:opencode"
"OPENCODE_CONFIG_DIR:*:opencode"
"OPENCODE_API_KEY:*:opencode"
"CLAUDE_CODE_AGENT_RULE_DISABLED:*:claude"
"CLAUDE_CODE_SSE_PORT:*:claude"
"AIDER_DARK_MODE:*:aider"
"CODEX_API_KEY:*:codex"
"CURSOR_AGENT_RULE_DISABLED:*:cursor"
"WINDSURF_AGENT_RULE_DISABLED:*:windsurf"
"CONTINUE_ON_ERROR:*:continue"
)
# Key = /proc/PID/comm (truncated to 15 chars on Linux).
# Value = canonical agent name.
declare -A AGENT_COMM
AGENT_COMM=(
[pi]="pi"
[opencode]="opencode"
[aider]="aider"
[claude]="claude"
[codex]="codex"
[cursor]="cursor"
[windsurf]="windsurf"
[continue]="continue"
[github-copilot]="github-copilot"
[copilot]="github-copilot"
[goose]="goose"
)
# For agents running under an interpreter (node, python, etc.).
# Key = substring to search in cmdline, Value = agent name.
declare -A AGENT_CMDLINE
AGENT_CMDLINE=(
["opencode"]="opencode"
["/pi "]="pi"
["/aider"]="aider"
["/claude"]="claude"
["/codex"]="codex"
["cursor"]="cursor"
["windsurf"]="windsurf"
["continue.dev"]="continue"
["github-copilot"]="github-copilot"
["goose"]="goose"
)
# Shells to skip when walking ancestors.
SHELL_COMMS=(
bash zsh fish sh dash ksh mksh posh ash tcsh csh
tmux:server tmux screen
sudo doas
)
SHELL_CMDLINE_SUBSTRINGS=(
"/bash" "/zsh" "/fish" "/sh" "/dash" "/ksh"
)
########################################################################
# Helpers
########################################################################
comm_of() {
local pid="$1"
cat "/proc/${pid}/comm" 2>/dev/null || true
}
cmdline_of() {
local pid="$1"
tr '\0' ' ' <"/proc/${pid}/cmdline" 2>/dev/null || true
}
is_shell() {
local pid="$1"
local c; c="$(comm_of "$pid")" || true
[[ -z "$c" ]] && return 1
for s in "${SHELL_COMMS[@]}"; do
[[ "$c" == "$s" ]] && return 0
done
local cl; cl="$(cmdline_of "$pid")" || true
for s in "${SHELL_CMDLINE_SUBSTRINGS[@]}"; do
[[ "$cl" == *"$s"* ]] && return 0
done
return 1
}
walk_to_agent_comm() {
local pid="$1"
local c
while [[ "$pid" -gt 1 ]]; do
c="$(comm_of "$pid")" || true
[[ -z "$c" ]] && break
if ! is_shell "$pid"; then
echo "$c"
return 0
fi
pid="$(awk '/^PPid:/{print $2}' "/proc/${pid}/status" 2>/dev/null || true)"
[[ -z "$pid" ]] && break
done
return 1
}
walk_to_agent_cmdline() {
local pid="$1"
local cl
while [[ "$pid" -gt 1 ]]; do
if ! is_shell "$pid"; then
cl="$(cmdline_of "$pid")" || true
if [[ -n "$cl" ]]; then
echo "$cl"
return 0
fi
fi
pid="$(awk '/^PPid:/{print $2}' "/proc/${pid}/status" 2>/dev/null || true)"
[[ -z "$pid" ]] && break
done
return 1
}
check_env_sigs() {
local var val agent
for sig in "${AGENT_ENV_SIGS[@]}"; do
IFS=':' read -r var val agent <<< "$sig"
if [[ "$val" == "*" ]]; then
if [[ -n "${!var:-}" ]]; then
echo "$agent"
return 0
fi
else
if [[ "${!var:-}" == "$val" ]]; then
echo "$agent"
return 0
fi
fi
done
return 1
}
match_cmdline() {
local cl="$1"
local needle agent
for needle in "${!AGENT_CMDLINE[@]}"; do
agent="${AGENT_CMDLINE[$needle]}"
if [[ "$cl" == *"$needle"* ]]; then
echo "$agent"
return 0
fi
done
return 1
}
match_comm() {
local c="$1"
local comm_name
for comm_name in "${!AGENT_COMM[@]}"; do
if [[ "$c" == "$comm_name" ]]; then
echo "${AGENT_COMM[$comm_name]}"
return 0
fi
done
return 1
}
########################################################################
# Main detection logic
########################################################################
detect() {
local agent=""
# Phase 1 — environment variables (most reliable)
agent="$(check_env_sigs)" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
# Phase 2 — walk ancestors, check comm
local ancestor_comm
ancestor_comm="$(walk_to_agent_comm "$PPID")" || true
if [[ -n "$ancestor_comm" ]]; then
agent="$(match_comm "$ancestor_comm")" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
# comm matched but not in our map; try its cmdline
local ancestor_pid="$PPID"
while [[ "$ancestor_pid" -gt 1 ]]; do
local ac; ac="$(comm_of "$ancestor_pid")" || true
if [[ "$ac" == "$ancestor_comm" ]]; then
local ancestor_cl
ancestor_cl="$(cmdline_of "$ancestor_pid")" || true
if [[ -n "$ancestor_cl" ]]; then
agent="$(match_cmdline "$ancestor_cl")" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
fi
break
fi
ancestor_pid="$(awk '/^PPid:/{print $2}' "/proc/${ancestor_pid}/status" 2>/dev/null || true)"
done
fi
# Phase 3 — walk ancestors, check cmdline (catches node-based agents)
local ancestor_cl
ancestor_cl="$(walk_to_agent_cmdline "$PPID")" || true
if [[ -n "$ancestor_cl" ]]; then
agent="$(match_cmdline "$ancestor_cl")" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
fi
# Phase 4 — fallback: check immediate PPID directly
local ppid_comm; ppid_comm="$(comm_of "$PPID")" || true
agent="$(match_comm "$ppid_comm")" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
local ppid_cl; ppid_cl="$(cmdline_of "$PPID")" || true
agent="$(match_cmdline "$ppid_cl")" || true
[[ -n "$agent" ]] && echo "$agent" && return 0
return 1
}
########################################################################
# Output
########################################################################
mode="${1:---name}"
case "$mode" in
--list-agents)
echo "Known agents (by env-var / process-name):"
echo ""
echo "Environment variable signatures:"
for sig in "${AGENT_ENV_SIGS[@]}"; do
IFS=':' read -r var val agent <<< "$sig"
printf " %-12s %-8s %s=%s\n" "$agent" "(env)" "$var" "$val"
done
echo ""
echo "Process comm signatures:"
for comm_name in "${!AGENT_COMM[@]}"; do
printf " %-12s %-8s %s\n" "${AGENT_COMM[$comm_name]}" "(comm)" "$comm_name"
done
echo ""
echo "Cmdline substring signatures:"
for needle in "${!AGENT_CMDLINE[@]}"; do
printf " %-12s %-8s *%s*\n" "${AGENT_CMDLINE[$needle]}" "(cmdline)" "$needle"
done
;;
--json)
agent="$(detect)" || agent="null"
if [[ "$agent" != "null" ]]; then
printf '{"agent":"%s","detected":true}\n' "$agent"
else
printf '{"agent":null,"detected":false}\n'
exit 1
fi
;;
--verbose)
echo "=== Agent Detection ==="
agent="$(detect)" || true
if [[ -n "$agent" ]]; then
echo "Detected agent: $agent"
else
echo "Detected agent: (unknown)"
fi
echo ""
echo "Relevant environment variables:"
for sig in "${AGENT_ENV_SIGS[@]}"; do
IFS=':' read -r var val _ <<< "$sig"
# Skip entries that aren't real variable names (e.g. patterns with *).
[[ "$var" == *\** ]] && continue
if [[ -n "${!var:-}" ]]; then
echo " $var=${!var}"
fi
done
[[ -z "${PI_CODING_AGENT:-}" ]] && echo " PI_CODING_AGENT=<unset>"
echo ""
echo "Process tree (up from PPID=$PPID):"
_dbg_pid="$PPID"
_dbg_depth=0
while [[ "$_dbg_pid" -gt 1 && "$_dbg_depth" -lt 15 ]]; do
_dbg_c="$(comm_of "$_dbg_pid")" || true
_dbg_cl="$(cmdline_of "$_dbg_pid")" || true
if [[ -n "$_dbg_c" ]]; then
printf " [%d] %s" "$_dbg_depth" "$_dbg_c"
if [[ -n "$_dbg_cl" ]]; then
printf " <- %s" "${_dbg_cl:0:100}"
fi
if [[ -n "$agent" ]]; then
_dbg_mc="$(match_comm "$_dbg_c")" || true
if [[ "$_dbg_mc" == "$agent" ]]; then
printf " <-- DETECTED"
fi
fi
echo ""
fi
_dbg_pid="$(awk '/^PPid:/{print $2}' "/proc/${_dbg_pid}/status" 2>/dev/null || true)"
_dbg_depth=$((_dbg_depth + 1))
done
;;
--name|*)
agent="$(detect)" || agent=""
if [[ -n "$agent" ]]; then
echo "$agent"
else
echo "unknown"
exit 1
fi
;;
esac