harnex 0.2.3 → 0.3.3
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 +4 -4
- data/GUIDE.md +24 -12
- data/README.md +78 -6
- data/bin/gem-push +20 -0
- data/lib/harnex/adapters/base.rb +2 -1
- data/lib/harnex/adapters/codex.rb +5 -1
- data/lib/harnex/cli.rb +3 -0
- data/lib/harnex/commands/run.rb +1 -0
- data/lib/harnex/commands/skills.rb +61 -13
- data/lib/harnex/runtime/session.rb +1 -0
- data/lib/harnex/version.rb +2 -1
- data/recipes/03_buddy.md +101 -0
- data/skills/harnex/SKILL.md +44 -0
- data/skills/harnex-buddy/SKILL.md +99 -0
- data/skills/{chain-implement → harnex-chain}/SKILL.md +3 -3
- data/skills/{dispatch → harnex-dispatch}/SKILL.md +18 -1
- metadata +10 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3c56e3cb64f20e24e32b7f46c4be98b23715407f5f990f79ba1b702720a5130b
|
|
4
|
+
data.tar.gz: 1c88ad05842287252453878522baba77db4b8388fa6166d3ef7eaf8127b0eaa1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 281b2f232bddcaf24d391a68fc331c73f7d43809d2f40d18848e417da9e50b999c330105a474f99ffa080528ccbeb6139da31c785604cffaab6ce76f4fe16584
|
|
7
|
+
data.tar.gz: 4e36010a2d17c5541e95c75179941b9fa282d2093237aabd0d1748d5b5c5c374eb4e67852d1d06b674ab039cdc7106c8f71de9bf64b9a99a737ec6e861b58fc6
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
209
|
-
commands.
|
|
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
|
-
|
|
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
|
-
|
|
220
|
-
harnex
|
|
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-
|
|
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
|
|
98
|
-
|
|
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
|
|
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
|
|
data/bin/gem-push
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
GEM_FILE="${1:?Usage: bin/gem-push <gemfile>}"
|
|
5
|
+
ENV_FILE="$(dirname "$0")/../.env"
|
|
6
|
+
|
|
7
|
+
if [[ ! -f "$ENV_FILE" ]]; then
|
|
8
|
+
echo "Error: .env not found" >&2
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
KEY=$(grep '^RUBYGEMS_TOTP_KEY=' "$ENV_FILE" | cut -d'"' -f2)
|
|
13
|
+
|
|
14
|
+
if [[ -z "$KEY" ]]; then
|
|
15
|
+
echo "Error: RUBYGEMS_TOTP_KEY not found in .env" >&2
|
|
16
|
+
exit 1
|
|
17
|
+
fi
|
|
18
|
+
|
|
19
|
+
OTP=$(echo "$KEY" | otp)
|
|
20
|
+
gem push "$GEM_FILE" --otp "$OTP"
|
data/lib/harnex/adapters/base.rb
CHANGED
|
@@ -124,8 +124,9 @@ module Harnex
|
|
|
124
124
|
end
|
|
125
125
|
|
|
126
126
|
def normalized_screen_text(screen_text)
|
|
127
|
-
text = screen_text.to_s.
|
|
127
|
+
text = screen_text.to_s.dup.force_encoding(Encoding::UTF_8).scrub("")
|
|
128
128
|
text = text.gsub(/\e\][^\a]*?(?:\a|\e\\)/, "")
|
|
129
|
+
text = text.gsub(/\e\[\d*(?:;1)?H/, "\n")
|
|
129
130
|
text = text.gsub(/\e(?:[@-Z\\-_]|\[[0-?]*[ -\/]*[@-~])/, "")
|
|
130
131
|
text.gsub(/\r\n?/, "\n")
|
|
131
132
|
end
|
|
@@ -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
|
-
|
|
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
data/lib/harnex/commands/run.rb
CHANGED
|
@@ -340,6 +340,7 @@ module Harnex
|
|
|
340
340
|
def tmux_name_arg?(argv, index, cli_name)
|
|
341
341
|
value = argv[index + 1]
|
|
342
342
|
return false if value.nil? || value == "--" || wrapper_option_token?(value)
|
|
343
|
+
return false if value.start_with?("--")
|
|
343
344
|
return true if cli_name
|
|
344
345
|
|
|
345
346
|
cli_candidate_after?(argv, index + 2)
|
|
@@ -3,17 +3,19 @@ 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-
|
|
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
|
|
11
|
+
Usage: harnex skills <subcommand> [--local]
|
|
11
12
|
|
|
12
13
|
Subcommands:
|
|
13
14
|
install Install bundled skills (globally by default)
|
|
15
|
+
uninstall Remove installed skills (globally by default)
|
|
14
16
|
|
|
15
17
|
Options:
|
|
16
|
-
--local
|
|
18
|
+
--local Target the current repo instead of global ~/.claude/
|
|
17
19
|
|
|
18
20
|
Installs: #{INSTALL_SKILLS.join(', ')}
|
|
19
21
|
|
|
@@ -33,13 +35,12 @@ module Harnex
|
|
|
33
35
|
subcommand = @argv.shift
|
|
34
36
|
case subcommand
|
|
35
37
|
when "install"
|
|
36
|
-
|
|
37
|
-
if help
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
38
|
+
local, help = parse_args(@argv)
|
|
39
|
+
return (puts self.class.usage; 0) if help
|
|
40
|
+
|
|
41
|
+
remove_deprecated(local)
|
|
41
42
|
|
|
42
|
-
|
|
43
|
+
INSTALL_SKILLS.each do |skill_name|
|
|
43
44
|
skill_source = resolve_skill_source(skill_name)
|
|
44
45
|
unless skill_source
|
|
45
46
|
return missing_skill(skill_name)
|
|
@@ -49,6 +50,14 @@ module Harnex
|
|
|
49
50
|
return result unless result == 0
|
|
50
51
|
end
|
|
51
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
|
|
52
61
|
when "-h", "--help", nil
|
|
53
62
|
puts self.class.usage
|
|
54
63
|
0
|
|
@@ -61,7 +70,7 @@ module Harnex
|
|
|
61
70
|
|
|
62
71
|
private
|
|
63
72
|
|
|
64
|
-
def
|
|
73
|
+
def parse_args(args)
|
|
65
74
|
local = false
|
|
66
75
|
help = false
|
|
67
76
|
|
|
@@ -74,12 +83,12 @@ module Harnex
|
|
|
74
83
|
when /\A-/
|
|
75
84
|
raise "harnex skills: unknown option #{arg.inspect}"
|
|
76
85
|
else
|
|
77
|
-
warn("harnex skills
|
|
78
|
-
raise "harnex skills
|
|
86
|
+
warn("harnex skills: unexpected argument #{arg.inspect}")
|
|
87
|
+
raise "harnex skills takes no positional arguments"
|
|
79
88
|
end
|
|
80
89
|
end
|
|
81
90
|
|
|
82
|
-
[
|
|
91
|
+
[local, help]
|
|
83
92
|
end
|
|
84
93
|
|
|
85
94
|
def resolve_skill_source(skill_name)
|
|
@@ -92,6 +101,12 @@ module Harnex
|
|
|
92
101
|
1
|
|
93
102
|
end
|
|
94
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
|
+
|
|
95
110
|
def install_local(skill_name, skill_source)
|
|
96
111
|
repo_root = Harnex.resolve_repo_root(Dir.pwd)
|
|
97
112
|
claude_dir = File.join(repo_root, ".claude", "skills", skill_name)
|
|
@@ -142,6 +157,39 @@ module Harnex
|
|
|
142
157
|
0
|
|
143
158
|
end
|
|
144
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
|
+
|
|
145
193
|
def relative_path(from:, to:)
|
|
146
194
|
from_parts = File.expand_path(from).split("/")
|
|
147
195
|
to_parts = File.expand_path(to).split("/")
|
data/lib/harnex/version.rb
CHANGED
data/recipes/03_buddy.md
ADDED
|
@@ -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.
|
data/skills/harnex/SKILL.md
CHANGED
|
@@ -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
|
|
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.
|
|
4
|
+
version: 0.3.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jikku Jose
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-04-23 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.
|
|
@@ -23,6 +23,7 @@ files:
|
|
|
23
23
|
- LICENSE
|
|
24
24
|
- README.md
|
|
25
25
|
- TECHNICAL.md
|
|
26
|
+
- bin/gem-push
|
|
26
27
|
- bin/harnex
|
|
27
28
|
- lib/harnex.rb
|
|
28
29
|
- lib/harnex/adapters.rb
|
|
@@ -54,9 +55,11 @@ files:
|
|
|
54
55
|
- lib/harnex/watcher/polling.rb
|
|
55
56
|
- recipes/01_fire_and_watch.md
|
|
56
57
|
- recipes/02_chain_implement.md
|
|
57
|
-
-
|
|
58
|
+
- recipes/03_buddy.md
|
|
58
59
|
- skills/close/SKILL.md
|
|
59
|
-
- skills/
|
|
60
|
+
- skills/harnex-buddy/SKILL.md
|
|
61
|
+
- skills/harnex-chain/SKILL.md
|
|
62
|
+
- skills/harnex-dispatch/SKILL.md
|
|
60
63
|
- skills/harnex/SKILL.md
|
|
61
64
|
- skills/open/SKILL.md
|
|
62
65
|
homepage: https://github.com/jikkuatwork/harnex
|
|
@@ -66,7 +69,7 @@ metadata:
|
|
|
66
69
|
homepage_uri: https://github.com/jikkuatwork/harnex
|
|
67
70
|
source_code_uri: https://github.com/jikkuatwork/harnex
|
|
68
71
|
bug_tracker_uri: https://github.com/jikkuatwork/harnex/issues
|
|
69
|
-
post_install_message:
|
|
72
|
+
post_install_message:
|
|
70
73
|
rdoc_options: []
|
|
71
74
|
require_paths:
|
|
72
75
|
- lib
|
|
@@ -82,7 +85,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
82
85
|
version: '0'
|
|
83
86
|
requirements: []
|
|
84
87
|
rubygems_version: 3.5.3
|
|
85
|
-
signing_key:
|
|
88
|
+
signing_key:
|
|
86
89
|
specification_version: 4
|
|
87
90
|
summary: PTY harness for terminal AI agents
|
|
88
91
|
test_files: []
|