Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
ea1a63e
fix(watch): add durable active watcher session
JTInventory Jun 27, 2026
5b68307
Merge pull request #5 from JTInventory/fm/controlled-baseline-sync-0627
JTInventory Jun 28, 2026
89a90b7
feat: add deterministic route resolver
JTInventory Jun 25, 2026
30a8f26
feat: record spawn route evidence
JTInventory Jun 25, 2026
4f9dfa0
fix route cleanup boundary matching
JTInventory Jun 26, 2026
6737f0b
fix: refuse stale task identity
JTInventory Jun 26, 2026
6101fc6
Merge pull request #6 from JTInventory/fm/adopt-pr85-route-resolver-0627
JTInventory Jun 28, 2026
f1b5f22
feat(cognee): adopt local lookup and source verification (#9)
JTInventory Jun 29, 2026
282ddb1
feat(cognee): add manual memory lookup helper (#10)
JTInventory Jun 29, 2026
708f980
fix: add durable watcher session runner (#11)
JTInventory Jun 29, 2026
c4212f3
Add backlog state drift audit (#13)
JTInventory Jun 29, 2026
654f9f8
feat(cognee): add cost telemetry guardrails (#14)
JTInventory Jun 29, 2026
edb6f7c
feat(cognee): add read-only live lookup (#16)
JTInventory Jun 29, 2026
24ca7ed
fix(cognee): send live search dataset selector (#17)
JTInventory Jun 29, 2026
fba775f
docs(briefs): add Cognee memory hint rules (#18)
JTInventory Jun 29, 2026
260499b
fix(cognee): harden telemetry schema (#19)
JTInventory Jun 29, 2026
d4345e8
fix: gate Cognee automatic lookup (#21)
JTInventory Jun 29, 2026
e3e9642
fix(cognee): load allowlisted env files safely (#22)
JTInventory Jun 29, 2026
3ba8dc2
Harden Cognee telemetry correlation (#23)
JTInventory Jun 30, 2026
b9a5df6
Add Cognee session cost probe helper (#24)
JTInventory Jun 30, 2026
ace28eb
Add read-only supervision model (#25)
JTInventory Jun 30, 2026
040a4f4
fix: keep Cognee session-window cost trial-only (#26)
JTInventory Jun 30, 2026
5330885
Harden Cognee policy and shell lint (#27)
JTInventory Jun 30, 2026
99941a2
feat: expand crew orchestration and X mode workflows (#28)
JTInventory Jun 30, 2026
bf20e47
fix: settle codex secondmate sends before submit (#29)
JTInventory Jul 1, 2026
bffd12b
fix: guard no-mistakes gh checks compatibility (#30)
JTInventory Jul 1, 2026
34dcafd
fix: prevent firstmate pr targeting upstream (#31)
JTInventory Jul 1, 2026
528ca21
fix: retry Codex secondmate sends (#32)
JTInventory Jul 1, 2026
746c596
test: use event-driven AFK injection waits (#33)
JTInventory Jul 1, 2026
4117d43
feat: add durable secondmate profile defaults (#34)
JTInventory Jul 1, 2026
e693483
fix: classify persistent supervision states (#35)
JTInventory Jul 2, 2026
04810bd
feat(stow): adopt operational learning capture (#36)
JTInventory Jul 3, 2026
3146925
fix: rearm watch session immediately after wakes (#37)
JTInventory Jul 3, 2026
87dfe83
fix: recover watcher locks after PID reuse (#38)
JTInventory Jul 8, 2026
6512675
fix: allow controlled fork no-mistakes guard (#39)
JTInventory Jul 8, 2026
b6926bc
feat: add Understand Anything orientation helper (#40)
JTInventory Jul 8, 2026
cd8d88f
docs: align Firstmate documentation and spawn-batch test guidance (#42)
JTInventory Jul 8, 2026
a1e6b71
feat(spawn): add JT PR intake governor (#41)
JTInventory Jul 9, 2026
5a34926
fix: honor persistent secondmates in backlog audit (#43)
JTInventory Jul 9, 2026
4319fb6
feat: consolidate firstmate loop tooling (#44)
JTInventory Jul 9, 2026
aef6973
fix: discover Axi tools in non-interactive shells (#45)
JTInventory Jul 9, 2026
1c3b1d4
feat: route dispatch profiles to GPT-5.6 (#46)
JTInventory Jul 10, 2026
a403a6b
fix: normalize user-local tool discovery across runtime scripts (#47)
JTInventory Jul 10, 2026
c818815
fix: normalize user-local tool discovery across runtime scripts (#47)
JTInventory Jul 10, 2026
b3e8f45
fix(lifecycle): gate PR merges on captain approval (#48)
JTInventory Jul 10, 2026
bf8c3c6
fix: preserve secondmate backlog handoffs (#49)
JTInventory Jul 10, 2026
94bf60a
fix(supervision): surface ready PRs and stabilize clone sync (#51)
JTInventory Jul 10, 2026
80def71
feat: remove Understand Anything integration (#52)
JTInventory Jul 10, 2026
e3bef62
fix(watcher): harden supervision cycle identity and attachment (#53)
JTInventory Jul 11, 2026
07d7c93
feat(cbm): add optional codebase memory orientation (#54)
JTInventory Jul 11, 2026
0647915
feat(cbm): add durable usage metering (#56)
JTInventory Jul 11, 2026
fab23e7
fix: retry transient git index locks during teardown (#55)
JTInventory Jul 11, 2026
e2559ae
fix: safely target tmux task windows by ID (#57)
JTInventory Jul 12, 2026
8392063
fix(bin): reject unresolved bare targets (#58)
JTInventory Jul 13, 2026
4d7d1c6
feat: classify declared external waits (#59)
JTInventory Jul 13, 2026
d9a7129
feat(supervision): re-surface declared external waits (#60)
JTInventory Jul 13, 2026
5da9351
fix(supervision): surface injection wedges in read-only state (#61)
JTInventory Jul 13, 2026
f793415
docs: record selective upstream adoption closeout (#62)
JTInventory Jul 13, 2026
8df2251
chore: align root-local ignore boundaries (#63)
JTInventory Jul 13, 2026
eeb7ddf
perf: parallelize and isolate local behavior tests (#64)
JTInventory Jul 14, 2026
03b176a
fix: prevent detached watcher reaping from blinding the fleet (#65)
JTInventory Jul 15, 2026
e988794
feat: add Phase A upstream adoption guards (#66)
JTInventory Jul 15, 2026
44283c1
fix: harden secondmate routing and turn-end safety (#67)
JTInventory Jul 15, 2026
2093311
feat: add paused-gate absorption and detached AFK supervision (#68)
JTInventory Jul 16, 2026
2c16965
feat: add selective operator comfort tooling (#69)
JTInventory Jul 16, 2026
de34b76
feat(backend): extract tmux runtime session-provider abstraction (#70)
JTInventory Jul 19, 2026
f14c5a1
feat: add experimental Herdr session backend (#71)
JTInventory Jul 19, 2026
5e7e68b
feat: support Herdr AFK supervisor injection (#72)
JTInventory Jul 19, 2026
a5ec062
feat(backend): deepen experimental Herdr adapter (#73)
JTInventory Jul 19, 2026
674ba4d
test(backend): add opt-in Herdr real-lab E2E coverage (#74)
JTInventory Jul 19, 2026
4f7b271
test: isolate Herdr behavior tests from ambient sessions (#75)
JTInventory Jul 20, 2026
a81dbfc
feat(herdr): add readable task tab labels (#76)
JTInventory Jul 24, 2026
4fbe587
fix(herdr): adopt upstream reliability while preserving fork contract…
JTInventory Jul 26, 2026
054f063
fix: execute every PR body compliance event (#78)
JTInventory Jul 26, 2026
9c6a4d8
fix: stop repeated wakes from merged PR polls (#79)
JTInventory Jul 26, 2026
52a66df
feat: add durable secondmate report recovery (#81)
JTInventory Jul 27, 2026
bd3060f
fix: manage Codex session locks across lifecycle hooks (#82)
JTInventory Jul 27, 2026
a667a02
fix: refine task intake and direct-PR recovery (#80)
JTInventory Jul 27, 2026
7bfe885
feat: add idea-fit scouting and scope contracts (#83)
JTInventory Jul 28, 2026
818c92a
feat: bind merge approval to exact presented PR state (#84)
JTInventory Jul 28, 2026
c270953
fix: preserve supervision state across multi-stage work (#85)
JTInventory Jul 29, 2026
7aa07ab
fix: reserve Grok watcher follower ownership (#86)
JTInventory Jul 31, 2026
098f5ac
fix: fleet-sync follows branch upstream on controlled forks (#89)
JTInventory Aug 6, 2026
cc7718c
fix: treat passed runs with skipped delivery as unlanded (#90)
JTInventory Aug 6, 2026
3921e3a
feat: enforce S1 worker and pooled-slot isolation (#91)
JTInventory Aug 12, 2026
fb01f25
fix: restore numeric helper library
JTInventory Aug 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .agents/skills/afk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,4 @@ These properties must hold:
`FM_INJECT_SKIP` (default `heartbeat`) force-self-handles matching kinds,
overriding classification.
Use it sparingly.

1 change: 1 addition & 0 deletions .agents/skills/fmx-respond/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ Inspect `state/x-outbox/` to see exactly what would have been posted.
- Never inline mention-influenced reply text into a shell command; always go through `--text-file` or stdin.
- The reply length authority is the relay (it trims), but a tight reply is on you.
- Never edit `bin/fm-x-poll.sh`, `bin/fm-x-reply.sh`, or the watcher to "answer faster"; the cadence is handled in bootstrap.

1 change: 1 addition & 0 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,4 @@ The decision persists per path in `~/.pi/agent/trust.json`, so later spawns in t
`fm-spawn` keeps the turn-end extension in `state/`, outside the worktree, because project-local extension files make the trust gate strictly worse and pollute the project.
The extension must listen for pi's `turn_end` event, not `agent_end`, so the watcher wakes after each completed turn instead of only when the whole agent run exits.
Pi sets `PI_CODING_AGENT=true` for its children; this is its harness-detection env marker.

1 change: 1 addition & 0 deletions .agents/skills/secondmate-provisioning/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,4 @@ If `treehouse return` fails for a leased home, teardown stops with state intact
With `--force`, teardown is the explicit discard path.
It kills child windows, discards child work and state inside the secondmate home, removes the route, releases the lease, and removes the retired secondmate home.
Never use `--force` unless the captain explicitly said to discard the work.

1 change: 1 addition & 0 deletions .agents/skills/stuck-crewmate-recovery/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ Escalate in order:
A low context reading is not wedging; modern harnesses auto-compact and keep going.
The worktree and commits persist, so relaunch is cheap.
5. If a second relaunch fails too, write `failed` to the backlog and tell the captain with evidence.

1 change: 1 addition & 0 deletions .agents/skills/updatefirstmate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,4 @@ This touches only the firstmate repo and its own worktrees, never anything under
- **Secondmates are never disrupted.**
A secondmate gets a tracked-files fast-forward (safe while it is mid-task, since its work lives in gitignored operational dirs and separate project worktrees) plus a gentle re-read nudge.
It is never torn down, interrupted, or forced.

50 changes: 40 additions & 10 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,59 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: shellcheck bin/*.sh tests/*.sh
- name: Install pinned ShellCheck
run: |
set -eu
bin/fm-install-shellcheck.sh "$RUNNER_TEMP/bin"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- run: bin/fm-lint.sh

tests:
name: Behavior tests
runs-on: ubuntu-latest
# The suite should finish in ~2-3 minutes; this generous cap fails loudly on a
# hung watcher or tmux test instead of riding GitHub's 360-minute default.
timeout-minutes: 15
timeout-minutes: 25
steps:
- uses: actions/checkout@v6
- name: Require tmux for e2e tests
- name: Install pinned ShellCheck
run: |
set -eu
bin/fm-install-shellcheck.sh "$RUNNER_TEMP/bin"
echo "$RUNNER_TEMP/bin" >> "$GITHUB_PATH"
- name: Require tmux for focused endpoint tests
run: |
set -eu
command -v tmux >/dev/null || {
echo "::error::tmux is required for real afk injection e2e coverage"
echo "::error::tmux is required for focused endpoint tests"
exit 1
}
tmux -V
- run: |
- name: Run focused isolation and slot tests
run: |
set -eu
bash tests/fm-worker-isolation.test.sh
bash tests/fm-watch-session.test.sh
bash tests/fm-slot-occupant-proof.test.sh
FM_TEARDOWN_TEST_FOCUS=s1 bash tests/fm-teardown.test.sh

macos-stock-bash:
name: Stock macOS Bash pooled-slot compatibility
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Run pooled-slot checks with stock Bash
shell: /bin/bash {0}
env:
PATH: /bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin
run: |
set -eu
for test_script in tests/*.test.sh; do
"$test_script"
done
case "$BASH_VERSION" in
3.2.57*) ;;
*) echo "::error::expected stock macOS Bash 3.2.57, got $BASH_VERSION"; exit 1 ;;
esac
/bin/bash --version | head -1
/bin/bash -n bin/fm-slot-owner-lib.sh
/bin/bash tests/fm-worker-isolation.test.sh

invariants:
name: Repo invariants
Expand Down
27 changes: 25 additions & 2 deletions .github/workflows/no-mistakes-required.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
name: Require no-mistakes
run-name: "PR #${{ github.event.pull_request.number }} body compliance - ${{ github.event.action }} - event ${{ github.run_number }} (run ${{ github.run_id }})"

on:
pull_request:
Expand All @@ -8,9 +9,14 @@ on:

permissions:
contents: read
pull-requests: read

# GitHub concurrency groups retain at most one pending run, replacing older
# pending runs even when cancel-in-progress is false. Give body-bearing events
# an immutable per-event group so first-time-fork approvals can never collapse
# opened/edited checks. Keep synchronize/reopened coalescing as before.
concurrency:
group: no-mistakes-required-${{ github.event.pull_request.number }}
group: no-mistakes-required-${{ github.event.pull_request.number }}-${{ (github.event.action == 'opened' || github.event.action == 'edited') && github.run_id || 'head-change' }}
cancel-in-progress: true

jobs:
Expand All @@ -26,13 +32,30 @@ jobs:
PR_BODY: ${{ github.event.pull_request.body }}
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
PR_NUMBER: ${{ github.event.pull_request.number }}
GH_TOKEN: ${{ github.token }}
run: |
set -eu
marker='Updates from [git push no-mistakes](https://github.com/kunchenguid/no-mistakes)'
if printf '%s' "${PR_BODY:-}" | grep -qF -- "$marker"; then
has_marker() {
printf '%s' "$1" | grep -qF -- "$marker"
}
if has_marker "${PR_BODY:-}"; then
echo "Found no-mistakes signature in PR #${PR_NUMBER} body."
exit 0
fi
# no-mistakes can push the branch and write the pipeline body close
# together; the pull_request payload can briefly lag the live body.
deadline=$(( $(date +%s) + 90 ))
while :; do
live_body=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq .body 2>/dev/null || true)
if has_marker "$live_body"; then
echo "Found no-mistakes signature in live PR #${PR_NUMBER} body."
exit 0
fi
now=$(date +%s)
[ "$now" -lt "$deadline" ] || break
sleep 5
done
{
echo "::error::This PR was not raised through no-mistakes."
echo
Expand Down
6 changes: 4 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ data/
.no-mistakes/
.lavish/
.DS_Store
/.fm-secondmate-home
.env
config/crew-harness
config/x-mode.env
/config/
/reports/
/backups/
11 changes: 11 additions & 0 deletions .no-mistakes.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
# Per-repo no-mistakes overrides.

# Gate agents must not load firstmate's project-level captain instructions. The
# lifecycle entrypoints also refuse the stamped gate marker and gate-repo path.
disable_project_settings: true

# Run the focused isolation and endpoint tests through the local runner instead
# of delegating to an agent.
commands:
lint: 'bin/fm-install-shellcheck.sh "${TMPDIR:-/tmp}/fm-shellcheck-$$/bin" && PATH="${TMPDIR:-/tmp}/fm-shellcheck-$$/bin:$PATH" bin/fm-lint.sh'
test: 'bash tests/fm-worker-isolation.test.sh && bash tests/fm-watch-session.test.sh && bash tests/fm-slot-occupant-proof.test.sh && FM_TEARDOWN_TEST_FOCUS=s1 bash tests/fm-teardown.test.sh'

# Keep test evidence out of this repo; it stays in a temp dir instead.
test:
evidence:
Expand Down
157 changes: 157 additions & 0 deletions bin/backends/herdr-eventwait.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
#!/usr/bin/env python3
"""Raw AF_UNIX subscriber for herdr's native pane.agent_status_changed stream.

This is the WIRE TRANSPORT half of the herdr push-escalation path
(bin/backends/herdr.sh fm_backend_herdr_wait_transition). It deliberately does
NOT know firstmate's supervision policy: it opens ONE connection to a herdr
session's control socket, subscribes to pane.agent_status_changed for the given
panes (all statuses, so working/idle/done edges are seen too), and prints one
projected line per event to stdout, flushing each so the bash caller can react
sub-second. The bash side normalizes each line through the shared transition
shape and applies the single-owner policy table (bin/fm-transition-lib.sh); the
bash side also decides when to stop and kills this reader.

Wire protocol (verified: herdr 0.7.3, protocol 16, newline-delimited JSON):
request : {"id","method":"events.subscribe","params":{"subscriptions":[
{"type":"pane.agent_status_changed","pane_id":P}, ...]}}\n
ack : {"id",...,"result":{"type":"subscription_started"}}\n
stream : {"event":"pane.agent_status_changed",
"data":{"pane_id","workspace_id","agent_status","agent",...}}\n

Usage: herdr-eventwait.py <socket_path> <timeout_seconds> <pane_id> [<pane_id> ...]

Output (one line per pane.agent_status_changed event, TAB-separated, a raw
projection - NOT the final normalized record; the bash normalizer adds the
from_status and builds the canonical shape):
@subscribed
<pane_id>\t<workspace_id>\t<agent_status>\t<agent>

Exit status:
0 streamed until the timeout elapsed with no error - a clean bounded wait;
the caller treats this as "no fast escalation, poll cadence preserved".
2 bad arguments, could not connect, or could not send the subscribe request.
3 the subscribe request did not return a subscription_started ack.
4 the server closed the stream early or a receive operation failed.
A non-zero exit tells the bash caller to fall back to plain polling for this
cycle (the permanent fail-closed backstop), never to go silent.
"""
import json
import socket
import sys
import time

CONNECT_TIMEOUT = 5.0
ACK_TIMEOUT = 5.0
RECV_CHUNK = 65536


def _read_line(sock, buf, deadline):
"""Read one newline-terminated chunk from sock, honoring an absolute
monotonic deadline. Returns (line_bytes_or_None, buf, outcome), where
outcome is line, timeout, closed, or error."""
while b"\n" not in buf:
remaining = deadline - time.monotonic()
if remaining <= 0:
return None, buf, "timeout"
sock.settimeout(remaining)
try:
chunk = sock.recv(RECV_CHUNK)
except socket.timeout:
return None, buf, "timeout"
except OSError:
return None, buf, "error"
if not chunk:
return None, buf, "closed"
buf += chunk
line, buf = buf.split(b"\n", 1)
return line, buf, "line"


def _clean(value):
return str(value).replace("\t", " ").replace("\r", " ").replace("\n", " ")


def main(argv):
if len(argv) < 4:
return 2
sock_path = argv[1]
try:
timeout = float(argv[2])
except ValueError:
return 2
panes = argv[3:]
if not panes or timeout <= 0:
return 2

try:
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(CONNECT_TIMEOUT)
sock.connect(sock_path)
except OSError:
return 2

subscriptions = [
{"type": "pane.agent_status_changed", "pane_id": pane} for pane in panes
]
request = {
"id": "fm-eventwait",
"method": "events.subscribe",
"params": {"subscriptions": subscriptions},
}
try:
sock.sendall((json.dumps(request) + "\n").encode("utf-8"))
except OSError:
return 2

start = time.monotonic()
deadline = start + timeout
buf = b""

# Bounded wait for the subscription_started ack (its own short budget, but
# never past the overall deadline).
ack_deadline = min(deadline, start + ACK_TIMEOUT)
line, buf, outcome = _read_line(sock, buf, ack_deadline)
if line is None:
return 2
try:
ack = json.loads(line.decode("utf-8", "replace"))
except ValueError:
return 3
result = ack.get("result") or {}
if result.get("type") != "subscription_started":
return 3

sys.stdout.write("@subscribed\n")
sys.stdout.flush()

# Stream projected events until the deadline or the server closes.
while True:
line, buf, outcome = _read_line(sock, buf, deadline)
if line is None:
return 0 if outcome == "timeout" else 4
try:
message = json.loads(line.decode("utf-8", "replace"))
except ValueError:
continue
if message.get("event") != "pane.agent_status_changed":
continue
data = message.get("data") or {}
fields = (
_clean(data.get("pane_id") or ""),
_clean(data.get("workspace_id") or ""),
_clean(data.get("agent_status") or ""),
_clean(data.get("agent") or ""),
)
sys.stdout.write("\t".join(fields) + "\n")
sys.stdout.flush()


if __name__ == "__main__":
try:
sys.exit(main(sys.argv))
except BrokenPipeError:
# The bash caller stopped reading (found its actionable edge and killed
# us). That is a normal, successful end of the wait.
sys.exit(0)
except KeyboardInterrupt:
sys.exit(0)
Loading