harnex 0.2.2 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f5551eb9f757a6028039163377a2a10bd044132513b582d50c94a5ababde1b4
4
- data.tar.gz: 82a6b0e9d329c94b40c2ee79ea4bd0a153cce8e5a246c208e27d7eee702d22f4
3
+ metadata.gz: acc43f4432f442abd435e2ef3278ca5e2ba94802285c67a50736aae9ea40188e
4
+ data.tar.gz: 0437ac12e301491856ceddb176c18ab9ac334c4dab31295d55dc4203c4a0f509
5
5
  SHA512:
6
- metadata.gz: cf269eb8b5bbf171154f0530d26f3135866e966e085c386326183432c4cf6b0c5cdd45f3585dd5632c7bd0db4098a54193c4d815bf4e1ab8256aa22c4f1aec9f
7
- data.tar.gz: be798ec3085903e0851d7cd20b31789eed202b076774a144ed8038d23d1d61aa12ac8c9489b04370498f3108d2138672a3db2a36ba4efdb3b7c1e7763b219626
6
+ metadata.gz: 5f8d1d3db623e3df0a0bbeb2f5f1f9e88c18ef2b6113355ad871e9c3fc3987c1e1b7dfb32bac5b79bd32697cd3c9f80bc6163398bfd8255eb57a984a47a548bc
7
+ data.tar.gz: 48e51e3630cbe866ac806e30eff42aa43ed551c8e90efd16dd03675c62d91f1e62dbddcea4a13220a7a7ec4473f4d513ec3cf25be2b6080cef32ca1552abd3cc
data/GUIDE.md CHANGED
@@ -161,9 +161,18 @@ After the worker finishes, inspect the screen:
161
161
  harnex pane --id review --lines 60
162
162
  ```
163
163
 
164
- Optional: if you're inside a harnex-managed session yourself and
165
- really want a callback, you can still use `$HARNEX_ID` as the return
166
- address. Treat that as secondary, not the main control flow.
164
+ **Return channel for any tmux pane:** Every spawned session gets
165
+ `$HARNEX_SPAWNER_PANE` the tmux pane ID of whoever ran `harnex run`.
166
+ The spawned agent can report back via `tmux send-keys`, even if the
167
+ invoker isn't a harnex session:
168
+
169
+ ```bash
170
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "done — results in /tmp/result.md" Enter
171
+ ```
172
+
173
+ If you're inside a harnex-managed session, you can also use
174
+ `$HARNEX_ID` as the return address with `harnex send`. But file
175
+ handoffs remain the preferred primary control flow.
167
176
 
168
177
  ## Stopping agents
169
178
 
@@ -205,21 +214,21 @@ an updated summary. Then review again with a fresh Claude instance.
205
214
 
206
215
  ## Teaching your agents about harnex
207
216
 
208
- Harnex ships a skill file that tells AI agents how to use harnex
209
- commands. To make it available globally:
217
+ Harnex ships skill files that tell AI agents how to use harnex
218
+ commands. Install them globally so every session picks them up:
210
219
 
211
220
  ```bash
212
- # For Claude Code
213
- ln -s /path/to/harnex/skills/harnex ~/.claude/skills/harnex
214
-
215
- # For Codex
216
- ln -s /path/to/harnex/skills/harnex ~/.codex/skills/harnex
221
+ harnex skills install
217
222
  ```
218
223
 
219
- After this, any Claude or Codex session — in any repo — can use
220
- harnex commands without being taught how. The skill activates
224
+ This copies the bundled skills (harnex-dispatch, harnex-chain,
225
+ harnex-buddy) to `~/.claude/skills/` and symlinks `~/.codex/skills/`
226
+ to them. After this, any Claude or Codex session — in any repo — can
227
+ use harnex commands without being taught how. The skills activate
221
228
  automatically when agent collaboration is needed.
222
229
 
230
+ For repo-local installs instead, use `--local`.
231
+
223
232
  ## Recipes
224
233
 
225
234
  Tested workflows for common multi-agent patterns. Read them
@@ -235,6 +244,9 @@ harnex recipes show 01 # read one
235
244
  - **Chain Implement** (`harnex recipes show 02`) — process a
236
245
  batch as repeated fire-and-watch: Codex plan/implement,
237
246
  Claude review, Codex fix, then review again if needed.
247
+ - **Buddy** (`harnex recipes show 03`) — spawn an accountability
248
+ partner for overnight or long-running work. The buddy polls the
249
+ worker's screen and nudges it if it stalls.
238
250
 
239
251
  ## What's next
240
252
 
data/README.md CHANGED
@@ -18,8 +18,8 @@ Then install workflow skills into your repo so agents can use them:
18
18
  harnex skills install
19
19
  ```
20
20
 
21
- This adds orchestration skills (dispatch, chain-implement) that Claude
22
- Code and Codex pick up automatically.
21
+ This adds orchestration skills (harnex-dispatch, harnex-chain, harnex-buddy)
22
+ that Claude Code and Codex pick up automatically.
23
23
 
24
24
  ## What it does
25
25
 
@@ -92,17 +92,78 @@ harnex stop --id cl-review
92
92
 
93
93
  Harnex ships workflow skills that automate this pattern:
94
94
 
95
- - **[Dispatch](skills/dispatch/SKILL.md)** — the fire-and-watch pattern:
95
+ - **[Dispatch](skills/harnex-dispatch/SKILL.md)** — the fire-and-watch pattern:
96
96
  spawn an agent, poll its screen, stop it when done
97
- - **[Chain Implement](skills/chain-implement/SKILL.md)** — end-to-end
98
- issue-to-code workflow: plan, review plan, implement, review code, fix
97
+ - **[Chain](skills/harnex-chain/SKILL.md)** — end-to-end issue-to-code
98
+ workflow: plan, review plan, implement, review code, fix
99
+ - **[Buddy](skills/harnex-buddy/SKILL.md)** — spawn an accountability partner
100
+ for long-running or overnight work
99
101
 
100
- Install skills into your repo so agents can use them:
102
+ Install skills so agents can use them:
101
103
 
102
104
  ```bash
103
105
  harnex skills install
104
106
  ```
105
107
 
108
+ ## Long-running and overnight work
109
+
110
+ A **buddy** is a second agent that watches something and acts on it.
111
+ It's just another harnex session — no special monitoring code, no
112
+ configuration. The buddy is an LLM, so it reasons about what it sees
113
+ rather than pattern-matching.
114
+
115
+ ### Example: keep a worker from stalling
116
+
117
+ Spawn a buddy alongside a long-running implementation worker:
118
+
119
+ ```bash
120
+ harnex run codex --id worker-42 --tmux
121
+ harnex run claude --id buddy-42 --tmux
122
+ harnex send --id buddy-42 --message "$(cat <<'EOF'
123
+ Watch harnex session worker-42.
124
+ Every 5 minutes: run `harnex pane --id worker-42 --lines 30`.
125
+ If it looks stuck at a prompt with no progress for 10+ minutes,
126
+ nudge it: `harnex send --id worker-42 --message "Continue your task."`.
127
+ When it exits, report back:
128
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 done" Enter
129
+ EOF
130
+ "
131
+ ```
132
+
133
+ ### Example: watch for doc drift during implementation
134
+
135
+ A buddy that checks whether a worker's code changes have left
136
+ docs out of date:
137
+
138
+ ```bash
139
+ harnex run codex --id worker-99 --tmux
140
+ harnex run claude --id buddy-99 --tmux
141
+ harnex send --id buddy-99 --message "$(cat <<'EOF'
142
+ Watch harnex session worker-99.
143
+ Every 5 minutes: run `harnex pane --id worker-99 --lines 30`.
144
+ When the worker goes idle after making changes, run `git diff --name-only`
145
+ and check whether any changed code has corresponding docs (README, GUIDE,
146
+ inline comments) that are now stale. If so, nudge the worker:
147
+ harnex send --id worker-99 --message "Docs may be stale — check README
148
+ sections related to <specific area>."
149
+ When the worker exits, report a summary to the invoker:
150
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-99 done. Doc drift: <yes/no>" Enter
151
+ EOF
152
+ "
153
+ ```
154
+
155
+ ### The invoker doesn't need to be a harnex session
156
+
157
+ Every spawned session gets `$HARNEX_SPAWNER_PANE` — the tmux pane ID
158
+ of whoever ran `harnex run`. The buddy can report back to a plain
159
+ Claude Code session, a Codex session, or any tmux pane:
160
+
161
+ ```bash
162
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 finished" Enter
163
+ ```
164
+
165
+ See [recipes/03_buddy.md](recipes/03_buddy.md) for the full pattern.
166
+
106
167
  ## All commands
107
168
 
108
169
  | Command | What it does |
@@ -117,6 +178,17 @@ harnex skills install
117
178
  | `harnex guide` | Getting started walkthrough |
118
179
  | `harnex recipes` | Tested workflow patterns |
119
180
  | `harnex skills install` | Install bundled skills for Claude/Codex |
181
+ | `harnex skills uninstall` | Remove installed skills |
182
+
183
+ ## Uninstalling
184
+
185
+ ```bash
186
+ harnex skills uninstall # remove skills from ~/.claude/ and ~/.codex/
187
+ gem uninstall harnex
188
+ ```
189
+
190
+ Run `harnex skills uninstall` before removing the gem — installed skills
191
+ persist in `~/.claude/skills/` and won't be cleaned up by `gem uninstall`.
120
192
 
121
193
  ## Going deeper
122
194
 
@@ -7,6 +7,7 @@ module Harnex
7
7
 
8
8
  def initialize(extra_args = [])
9
9
  super("codex", extra_args)
10
+ @banner_seen = false
10
11
  end
11
12
 
12
13
  def base_command
@@ -37,7 +38,10 @@ module Harnex
37
38
 
38
39
  def input_state(screen_text)
39
40
  lines = recent_lines(screen_text)
40
- return super unless lines.any? { |line| line.include?("OpenAI Codex") || line.include?("gpt-") }
41
+ if lines.any? { |line| line.include?("OpenAI Codex") || line.include?("gpt-") }
42
+ @banner_seen = true
43
+ end
44
+ return super unless @banner_seen
41
45
 
42
46
  if lines.any? { |line| prompt_line?(line) }
43
47
  {
data/lib/harnex/cli.rb CHANGED
@@ -32,6 +32,9 @@ module Harnex
32
32
  when "help"
33
33
  puts help(@argv[1])
34
34
  0
35
+ when "-v", "--version", "version"
36
+ puts "harnex #{Harnex::VERSION} (#{Harnex::RELEASE_DATE})"
37
+ 0
35
38
  when "-h", "--help"
36
39
  puts usage
37
40
  0
@@ -3,26 +3,27 @@ require "fileutils"
3
3
  module Harnex
4
4
  class Skills
5
5
  SKILLS_ROOT = File.expand_path("../../../../skills", __FILE__)
6
- INSTALL_SKILLS = %w[dispatch chain-implement].freeze
6
+ INSTALL_SKILLS = %w[harnex-dispatch harnex-chain harnex-buddy].freeze
7
+ DEPRECATED_SKILLS = %w[dispatch chain-implement].freeze
7
8
 
8
9
  def self.usage
9
10
  <<~TEXT
10
- Usage: harnex skills install [--global]
11
+ Usage: harnex skills <subcommand> [--local]
11
12
 
12
13
  Subcommands:
13
- install Install bundled skills into the current repo
14
+ install Install bundled skills (globally by default)
15
+ uninstall Remove installed skills (globally by default)
14
16
 
15
17
  Options:
16
- --global Install to ~/.claude/skills and ~/.codex/skills
17
- instead of the current repo
18
+ --local Target the current repo instead of global ~/.claude/
18
19
 
19
20
  Installs: #{INSTALL_SKILLS.join(', ')}
20
21
 
21
- Without --global, copies each skill to .claude/skills/<skill>/
22
- in the current repo and symlinks .codex/skills/<skill> to it.
22
+ By default, copies each skill to ~/.claude/skills/<skill>/
23
+ and symlinks ~/.codex/skills/<skill> to it.
23
24
 
24
- With --global, symlinks both ~/.claude/skills/<skill> and
25
- ~/.codex/skills/<skill> to the bundled source.
25
+ With --local, copies each skill to .claude/skills/<skill>/
26
+ in the current repo and symlinks .codex/skills/<skill> to it.
26
27
  TEXT
27
28
  end
28
29
 
@@ -34,22 +35,29 @@ module Harnex
34
35
  subcommand = @argv.shift
35
36
  case subcommand
36
37
  when "install"
37
- skill_names, global, help = parse_install_args(@argv)
38
- if help
39
- puts self.class.usage
40
- return 0
41
- end
38
+ local, help = parse_args(@argv)
39
+ return (puts self.class.usage; 0) if help
42
40
 
43
- skill_names.each do |skill_name|
41
+ remove_deprecated(local)
42
+
43
+ INSTALL_SKILLS.each do |skill_name|
44
44
  skill_source = resolve_skill_source(skill_name)
45
45
  unless skill_source
46
46
  return missing_skill(skill_name)
47
47
  end
48
48
 
49
- result = global ? install_global(skill_name, skill_source) : install_local(skill_name, skill_source)
49
+ result = local ? install_local(skill_name, skill_source) : install_global(skill_name, skill_source)
50
50
  return result unless result == 0
51
51
  end
52
52
  0
53
+ when "uninstall"
54
+ local, help = parse_args(@argv)
55
+ return (puts self.class.usage; 0) if help
56
+
57
+ (INSTALL_SKILLS + DEPRECATED_SKILLS).each do |skill_name|
58
+ local ? uninstall_local(skill_name) : uninstall_global(skill_name)
59
+ end
60
+ 0
53
61
  when "-h", "--help", nil
54
62
  puts self.class.usage
55
63
  0
@@ -62,25 +70,25 @@ module Harnex
62
70
 
63
71
  private
64
72
 
65
- def parse_install_args(args)
66
- global = false
73
+ def parse_args(args)
74
+ local = false
67
75
  help = false
68
76
 
69
77
  args.each do |arg|
70
78
  case arg
71
- when "--global"
72
- global = true
79
+ when "--local"
80
+ local = true
73
81
  when "-h", "--help"
74
82
  help = true
75
83
  when /\A-/
76
84
  raise "harnex skills: unknown option #{arg.inspect}"
77
85
  else
78
- warn("harnex skills install: unexpected argument #{arg.inspect}")
79
- raise "harnex skills install takes no positional arguments"
86
+ warn("harnex skills: unexpected argument #{arg.inspect}")
87
+ raise "harnex skills takes no positional arguments"
80
88
  end
81
89
  end
82
90
 
83
- [INSTALL_SKILLS, global, help]
91
+ [local, help]
84
92
  end
85
93
 
86
94
  def resolve_skill_source(skill_name)
@@ -93,6 +101,12 @@ module Harnex
93
101
  1
94
102
  end
95
103
 
104
+ def remove_deprecated(local)
105
+ DEPRECATED_SKILLS.each do |skill_name|
106
+ local ? uninstall_local(skill_name) : uninstall_global(skill_name)
107
+ end
108
+ end
109
+
96
110
  def install_local(skill_name, skill_source)
97
111
  repo_root = Harnex.resolve_repo_root(Dir.pwd)
98
112
  claude_dir = File.join(repo_root, ".claude", "skills", skill_name)
@@ -124,17 +138,58 @@ module Harnex
124
138
  claude_dir = File.expand_path("~/.claude/skills/#{skill_name}")
125
139
  codex_dir = File.expand_path("~/.codex/skills/#{skill_name}")
126
140
 
127
- [claude_dir, codex_dir].each do |dir|
128
- parent = File.dirname(dir)
129
- FileUtils.mkdir_p(parent)
130
- FileUtils.rm_rf(dir) if File.exist?(dir) || File.symlink?(dir)
131
- File.symlink(skill_source, dir)
132
- puts "symlinked #{dir} -> #{skill_source}"
141
+ # Copy skill to ~/.claude/skills/<skill>/
142
+ if Dir.exist?(claude_dir)
143
+ warn("harnex skills: #{claude_dir} already exists, overwriting")
144
+ FileUtils.rm_rf(claude_dir)
133
145
  end
146
+ FileUtils.mkdir_p(File.dirname(claude_dir))
147
+ FileUtils.cp_r(skill_source, claude_dir)
148
+ puts "installed #{claude_dir}"
149
+
150
+ # Symlink ~/.codex/skills/<skill> -> ~/.claude/skills/<skill>
151
+ codex_parent = File.dirname(codex_dir)
152
+ FileUtils.mkdir_p(codex_parent)
153
+ FileUtils.rm_rf(codex_dir) if File.exist?(codex_dir) || File.symlink?(codex_dir)
154
+ File.symlink(claude_dir, codex_dir)
155
+ puts "symlinked #{codex_dir} -> #{claude_dir}"
134
156
 
135
157
  0
136
158
  end
137
159
 
160
+ def uninstall_local(skill_name)
161
+ repo_root = Harnex.resolve_repo_root(Dir.pwd)
162
+ claude_dir = File.join(repo_root, ".claude", "skills", skill_name)
163
+ codex_dir = File.join(repo_root, ".codex", "skills", skill_name)
164
+
165
+ removed = false
166
+ if File.exist?(codex_dir) || File.symlink?(codex_dir)
167
+ FileUtils.rm_rf(codex_dir)
168
+ removed = true
169
+ end
170
+ if File.exist?(claude_dir) || File.symlink?(claude_dir)
171
+ FileUtils.rm_rf(claude_dir)
172
+ removed = true
173
+ end
174
+ puts "removed #{skill_name}" if removed
175
+ end
176
+
177
+ def uninstall_global(skill_name)
178
+ claude_dir = File.expand_path("~/.claude/skills/#{skill_name}")
179
+ codex_dir = File.expand_path("~/.codex/skills/#{skill_name}")
180
+
181
+ removed = false
182
+ if File.exist?(codex_dir) || File.symlink?(codex_dir)
183
+ FileUtils.rm_rf(codex_dir)
184
+ removed = true
185
+ end
186
+ if File.exist?(claude_dir) || File.symlink?(claude_dir)
187
+ FileUtils.rm_rf(claude_dir)
188
+ removed = true
189
+ end
190
+ puts "removed #{skill_name}" if removed
191
+ end
192
+
138
193
  def relative_path(from:, to:)
139
194
  from_parts = File.expand_path(from).split("/")
140
195
  to_parts = File.expand_path(to).split("/")
@@ -192,6 +192,7 @@ module Harnex
192
192
  "HARNEX_SESSION_REPO_ROOT" => repo_root
193
193
  }
194
194
  env["HARNEX_DESCRIPTION"] = description if description
195
+ env["HARNEX_SPAWNER_PANE"] = ENV["TMUX_PANE"] if ENV["TMUX_PANE"]
195
196
  env
196
197
  end
197
198
 
@@ -1,3 +1,4 @@
1
1
  module Harnex
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.1"
3
+ RELEASE_DATE = "2026-04-19"
3
4
  end
@@ -0,0 +1,101 @@
1
+ # Recipe: Buddy
2
+
3
+ Spawn an accountability partner for a long-running session.
4
+
5
+ The buddy is a separate harnex agent whose only job is to periodically
6
+ check on the worker and nudge it if it stalls. The buddy is an LLM, so
7
+ it has intelligence for free — it can read the worker's screen, reason
8
+ about whether it's stuck, and compose a meaningful nudge.
9
+
10
+ No special monitoring code, no configuration. Just another harnex
11
+ session using existing primitives.
12
+
13
+ ## When to use
14
+
15
+ - Overnight or multi-hour pipelines
16
+ - Any work where you won't be watching and want recovery from stalls
17
+ - When the invoking agent (you) dispatches a worker and wants assurance
18
+ it won't die silently
19
+
20
+ ## Steps
21
+
22
+ ### 1. Spawn the worker
23
+
24
+ ```bash
25
+ harnex run codex --id worker-42 --tmux worker-42
26
+ harnex send --id worker-42 --message "Read and execute /tmp/task-42.md"
27
+ ```
28
+
29
+ ### 2. Spawn its buddy
30
+
31
+ ```bash
32
+ harnex run claude --id buddy-42 --tmux buddy-42
33
+ ```
34
+
35
+ ### 3. Give the buddy its instructions
36
+
37
+ ```bash
38
+ cat > /tmp/buddy-42.md <<'EOF'
39
+ You are an accountability partner for harnex session `worker-42`.
40
+
41
+ Your job:
42
+ 1. Every 5 minutes, check on the worker:
43
+ - `harnex pane --id worker-42 --lines 30`
44
+ - `harnex status --id worker-42 --json`
45
+ 2. If the worker appears stuck at a prompt for more than 10 minutes
46
+ with no progress, nudge it:
47
+ - `harnex send --id worker-42 --message "You appear to have stalled. Continue with your current task."`
48
+ 3. If the worker has exited (status shows no session), report back:
49
+ - `tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 has exited. Check results." Enter`
50
+ 4. Keep watching until the worker finishes or is stopped.
51
+
52
+ Do not interfere with work in progress. Only nudge when clearly stalled.
53
+ EOF
54
+
55
+ harnex send --id buddy-42 --message "Read and execute /tmp/buddy-42.md"
56
+ ```
57
+
58
+ ## Return channel
59
+
60
+ The buddy can reach its invoker (your raw Claude session) via
61
+ `$HARNEX_SPAWNER_PANE` — the tmux pane ID of whoever ran `harnex run`.
62
+ This works even if the invoker is not a harnex session:
63
+
64
+ ```bash
65
+ # Buddy reads the invoker's screen
66
+ tmux capture-pane -t "$HARNEX_SPAWNER_PANE" -p
67
+
68
+ # Buddy types into the invoker
69
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 finished, all tests pass" Enter
70
+ ```
71
+
72
+ ## Naming convention
73
+
74
+ | Role | ID pattern | Example |
75
+ |------|-----------|---------|
76
+ | Worker | `worker-NN` | `worker-42` |
77
+ | Buddy | `buddy-NN` | `buddy-42` |
78
+
79
+ Match the buddy ID to the worker it watches.
80
+
81
+ ## Cleanup
82
+
83
+ Stop the buddy after the worker finishes:
84
+
85
+ ```bash
86
+ harnex stop --id buddy-42
87
+ ```
88
+
89
+ Or tell the buddy to self-stop in its instructions: "When the worker
90
+ exits, run `harnex stop --id buddy-42` on yourself."
91
+
92
+ ## Notes
93
+
94
+ - The buddy is a regular harnex session. Inspect it with `harnex pane`,
95
+ stop it with `harnex stop`, check it with `harnex status`.
96
+ - For multiple workers, spawn one buddy per worker or one buddy that
97
+ watches several sessions.
98
+ - The buddy's intelligence comes from being an LLM. It doesn't
99
+ pattern-match — it reads the screen and reasons about what to do.
100
+ - Tune the polling interval and stall threshold in the buddy's prompt,
101
+ not in harnex configuration.
@@ -24,10 +24,23 @@ Check environment variables to understand your role:
24
24
  | `HARNEX_ID` | Your session ID |
25
25
  | `HARNEX_SESSION_REPO_ROOT` | Repo root this session is scoped to |
26
26
  | `HARNEX_SESSION_ID` | Internal instance identifier |
27
+ | `HARNEX_SPAWNER_PANE` | Tmux pane ID (`%N`) of whoever spawned this session |
27
28
 
28
29
  If these are set, you are **inside a harnex session** and can send messages to
29
30
  peer sessions or spawn new worker sessions.
30
31
 
32
+ `HARNEX_SPAWNER_PANE` is the stable tmux pane ID of the invoker — use it to
33
+ reach back to the session that launched you, even if that session is not
34
+ harnex-managed:
35
+
36
+ ```bash
37
+ # Read the invoker's screen
38
+ tmux capture-pane -t "$HARNEX_SPAWNER_PANE" -p
39
+
40
+ # Type into the invoker
41
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "done — results in /tmp/result.md" Enter
42
+ ```
43
+
31
44
  ## Mode preference
32
45
 
33
46
  When starting another agent session for the user, default to a visible tmux
@@ -284,6 +297,37 @@ Sessions can watch a shared file (e.g. `--watch ./tmp/tick.jsonl`). When the
284
297
  file changes, harnex injects a `file-change-hook: read <path>` message. If you
285
298
  receive this hook, read the file and act on its contents.
286
299
 
300
+ ## Buddy pattern — accountability for long-running work
301
+
302
+ For any work that will take a long time (overnight pipelines, multi-hour
303
+ implementations, unattended batch jobs), spawn a **buddy** — a second harnex
304
+ session that watches the worker and nudges it if it stalls.
305
+
306
+ ```bash
307
+ # Spawn the worker
308
+ harnex run codex --id worker-42 --tmux worker-42
309
+ harnex send --id worker-42 --message "Read and execute /tmp/task-42.md"
310
+
311
+ # Spawn a buddy to watch it
312
+ harnex run claude --id buddy-42 --tmux buddy-42
313
+ harnex send --id buddy-42 --message "Read and execute /tmp/buddy-42.md"
314
+ ```
315
+
316
+ The buddy's prompt tells it: poll `harnex pane` and `harnex status` every N
317
+ minutes, nudge with `harnex send` if the worker stalls, report back to
318
+ `$HARNEX_SPAWNER_PANE` when done.
319
+
320
+ See `recipes/03_buddy.md` for the full pattern.
321
+
322
+ **When to spawn a buddy:**
323
+ - The user says "do this overnight" or "run this while I'm away"
324
+ - The task is expected to take more than 30 minutes unattended
325
+ - The user explicitly asks for a buddy or accountability partner
326
+
327
+ **When NOT to spawn a buddy:**
328
+ - Short tasks you're actively watching
329
+ - The user hasn't asked for long-running autonomy
330
+
287
331
  ## Important rules
288
332
 
289
333
  1. **Always confirm with the user before sending** unless they explicitly asked
@@ -0,0 +1,99 @@
1
+ ---
2
+ name: harnex-buddy
3
+ description: Spawn an accountability partner for long-running harnex sessions. Use when the user asks to run something overnight, unattended, or for any work expected to take more than 30 minutes without supervision.
4
+ ---
5
+
6
+ # Buddy — Accountability Partner for Long-Running Work
7
+
8
+ For any long-running or unattended work, spawn a **buddy** — a second harnex
9
+ agent that watches the worker and nudges it if it stalls.
10
+
11
+ The buddy is an LLM, so it has intelligence for free. It reads the worker's
12
+ screen, reasons about whether it's stuck, and composes a meaningful nudge.
13
+
14
+ ## When to activate
15
+
16
+ - User says "do this overnight" or "run this while I'm away"
17
+ - Task is expected to take more than 30 minutes unattended
18
+ - User explicitly asks for a buddy, accountability partner, or monitoring
19
+ - User asks to "keep an eye on" a dispatched worker
20
+
21
+ ## Spawn the buddy
22
+
23
+ After dispatching the worker, spawn a buddy alongside it:
24
+
25
+ ```bash
26
+ # Worker already running
27
+ harnex run codex --id worker-42 --tmux worker-42
28
+
29
+ # Spawn its buddy
30
+ harnex run claude --id buddy-42 --tmux buddy-42
31
+ ```
32
+
33
+ ## Write the buddy prompt
34
+
35
+ Write a task file with the watching instructions, then send it:
36
+
37
+ ```bash
38
+ cat > /tmp/buddy-42.md <<'EOF'
39
+ You are an accountability partner for harnex session `worker-42`.
40
+
41
+ Your job:
42
+ 1. Every 5 minutes, check on the worker:
43
+ - `harnex pane --id worker-42 --lines 30`
44
+ - `harnex status --id worker-42 --json`
45
+ 2. If the worker appears stuck at a prompt for more than 10 minutes
46
+ with no progress, nudge it:
47
+ - `harnex send --id worker-42 --message "You appear to have stalled. Continue with your current task."`
48
+ 3. If the worker has exited, report back to the invoker:
49
+ - `tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 has exited. Check results." Enter`
50
+ 4. Keep watching until the worker finishes or is stopped.
51
+
52
+ Do not interfere with work in progress. Only nudge when clearly stalled.
53
+ EOF
54
+
55
+ harnex send --id buddy-42 --message "Read and execute /tmp/buddy-42.md"
56
+ ```
57
+
58
+ Adjust the polling interval (5 min), stall threshold (10 min), and nudge
59
+ message to match the workload.
60
+
61
+ ## Return channel
62
+
63
+ The buddy can reach back to the invoker (your raw Claude session) via
64
+ `$HARNEX_SPAWNER_PANE` — the stable tmux pane ID set automatically by
65
+ harnex at spawn time:
66
+
67
+ ```bash
68
+ # Read the invoker's screen
69
+ tmux capture-pane -t "$HARNEX_SPAWNER_PANE" -p
70
+
71
+ # Type into the invoker
72
+ tmux send-keys -t "$HARNEX_SPAWNER_PANE" "worker-42 finished" Enter
73
+ ```
74
+
75
+ The invoker does NOT need to be a harnex session. It just needs to be in tmux.
76
+
77
+ ## Naming convention
78
+
79
+ | Role | ID pattern | Example |
80
+ |------|-----------|---------|
81
+ | Worker | `worker-NN` | `worker-42` |
82
+ | Buddy | `buddy-NN` | `buddy-42` |
83
+
84
+ Match the buddy ID to the worker it watches.
85
+
86
+ ## Cleanup
87
+
88
+ Stop the buddy after the worker finishes:
89
+
90
+ ```bash
91
+ harnex stop --id buddy-42
92
+ ```
93
+
94
+ ## Notes
95
+
96
+ - One buddy per worker, or one buddy watching multiple sessions
97
+ - The buddy is a regular harnex session — stop, inspect, log it like any other
98
+ - Tune polling and thresholds in the buddy's prompt, not in harnex config
99
+ - See `recipes/03_buddy.md` for the full recipe
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: chain-implement
2
+ name: harnex-chain
3
3
  description: End-to-end workflow from issue to shipped plans via harnex agents. Covers mapping, plan extraction, and the serial plan → review → implement → review → fix loop.
4
4
  ---
5
5
 
@@ -138,7 +138,7 @@ correct.
138
138
 
139
139
  ### Dispatch pattern
140
140
 
141
- For each plan NN, use the Fire & Watch pattern from the `dispatch` skill:
141
+ For each plan NN, use the Fire & Watch pattern from the `harnex-dispatch` skill:
142
142
 
143
143
  ```bash
144
144
  # Steps 1-3: Plan convergence (skip if plan already extracted and reviewed)
@@ -211,7 +211,7 @@ By default, all work happens serially on master. Use worktrees only when:
211
211
  - The user explicitly requests isolation
212
212
  - You need to work on something else while a plan is being implemented
213
213
 
214
- See the `dispatch` skill for worktree setup and caveats.
214
+ See the `harnex-dispatch` skill for worktree setup and caveats.
215
215
 
216
216
  ## When Things Go Wrong
217
217
 
@@ -1,5 +1,5 @@
1
1
  ---
2
- name: dispatch
2
+ name: harnex-dispatch
3
3
  description: Fire & Watch — the standard pattern for launching and monitoring harnex agent sessions. Use when dispatching implementation, review, or fix agents.
4
4
  ---
5
5
 
@@ -161,6 +161,23 @@ harnex status # current repo sessions
161
161
  harnex status --all # all repos
162
162
  ```
163
163
 
164
+ ## Buddy for Long-Running Dispatches
165
+
166
+ If the dispatched work is expected to take a long time (overnight, multi-hour)
167
+ or the user asks for unattended execution, spawn a buddy alongside the worker:
168
+
169
+ ```bash
170
+ # Worker
171
+ harnex run codex --id cx-impl-NN --tmux cx-impl-NN \
172
+ --context "Implement koder/plans/NN_name.md. Run tests when done."
173
+
174
+ # Buddy to watch it
175
+ harnex run claude --id buddy-NN --tmux buddy-NN
176
+ harnex send --id buddy-NN --message "Watch session cx-impl-NN. Poll every 5 min with harnex pane --id cx-impl-NN --lines 20. Nudge with harnex send if stuck for >10 min. Report back to \$HARNEX_SPAWNER_PANE when done."
177
+ ```
178
+
179
+ The buddy replaces manual Fire & Watch polling. See `recipes/03_buddy.md`.
180
+
164
181
  ## What NOT to Do
165
182
 
166
183
  - **Never** launch agents with raw `tmux send-keys` or `tmux new-window`
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harnex
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jikku Jose
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-03-31 00:00:00.000000000 Z
11
+ date: 2026-04-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A local PTY harness that wraps terminal AI agents (Claude, Codex) and
14
14
  adds a control plane for discovery, messaging, and coordination.
@@ -54,9 +54,11 @@ files:
54
54
  - lib/harnex/watcher/polling.rb
55
55
  - recipes/01_fire_and_watch.md
56
56
  - recipes/02_chain_implement.md
57
- - skills/chain-implement/SKILL.md
57
+ - recipes/03_buddy.md
58
58
  - skills/close/SKILL.md
59
- - skills/dispatch/SKILL.md
59
+ - skills/harnex-buddy/SKILL.md
60
+ - skills/harnex-chain/SKILL.md
61
+ - skills/harnex-dispatch/SKILL.md
60
62
  - skills/harnex/SKILL.md
61
63
  - skills/open/SKILL.md
62
64
  homepage: https://github.com/jikkuatwork/harnex