Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
116 changes: 27 additions & 89 deletions .agents/skills/firstmate-codexapp/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,110 +1,48 @@
---
name: firstmate-codexapp
description: >-
Agent-only playbook for coordinating visible Codex Desktop threads alongside Firstmate without pretending they are a selectable shell backend.
Use before creating, reading, steering, archiving, debugging, or reviewing a Codex App visible thread for Firstmate work, and before responding to requests to make Codex App native to Firstmate.
Agent-only playbook for creating, steering, supervising, recovering, and retiring Codex Desktop-visible Firstmate workers through the codex-app backend.
user-invocable: false
metadata:
internal: true
---

# firstmate-codexapp

## Overview
Use this playbook before operating a Codex Desktop-visible Firstmate worker.
Read `docs/codex-app-backend.md` for the current setup and limits.
Load `harness-adapters` before spawn, recovery, steering, interruption, or teardown as usual.

Use this playbook when Firstmate work needs a visible Codex Desktop thread.
The current supported shape is Desktop host-tool choreography plus an explicit status-file return-channel check, not a `codex-app` value in `FM_BACKEND`.
## Dispatch

## Boundary
Resolve the normal task brief and project first.
Select `backend=codex-app` only with `harness=codex`.
It supports ship and scout tasks, not secondmates.
Do not substitute Desktop host-tool choreography for the backend when a durable Firstmate task is required.

Codex Desktop visible threads are companion host-tool workflows, not a selectable Firstmate backend.
Read `docs/codex-app-backend.md` when it exists in this checkout; that document owns the acceptance contract, bridge requirement, status-return requirement, and staged rollout.
Spawn through the normal entry point:

If local helper scripts exist for Codex App work, use only helpers explicitly provided by the operator or maintained by Firstmate.
For helpers outside `bin/`, inspect the source or header before running `--help`.

## Preflight

1. Confirm this session is running inside Codex Desktop and that the host tools are exposed.
Search exact names when needed: `create_thread`, `list_threads`, `read_thread`, `send_message_to_thread`, `archive`, and `set_thread_archived`.
2. Confirm the target repository is already saved as a Codex Desktop project.
No host tool currently creates Codex App projects for an agent, so the human must add the project in Desktop before a created thread can reliably land there.
3. Do not create projectless threads for repo work.
If the project is absent, stop and ask for the project to be added or use a normal Firstmate backend instead.
4. Decide whether this is a real Firstmate-managed task or a visible companion thread.
A real task needs a task id, an isolated worktree or Desktop-owned cwd, a branch plan, and a writable `state/<id>.status` path.

## Create And Send

When creating a visible thread, use the Desktop host tool, not shell imitation.
Target the saved project and ask the worker to start by reporting:

```text
pwd
git rev-parse --show-toplevel
git branch --show-current
git log --oneline --max-count=3
```sh
bin/fm-spawn.sh <task-id> <project> --harness codex --backend codex-app
```

For writable repo work, instruct the worker to use the Codex-created current directory.
Do not tell it to `cd` into the saved project checkout for edits, commits, no-mistakes, pushes, or PR work.

When sending follow-up instructions, use `send_message_to_thread`.
If the user types directly into the visible thread, treat that as authoritative and reconcile from `read_thread` instead of undoing it.

## Status Return Channel

A Desktop-owned Codex thread can append to Firstmate status files only when the prompt gives an absolute path and the Desktop permission context can write that checkout.
That makes status writes a verified return-channel requirement, not a fact to assume.

For a Firstmate-managed task, include an explicit status instruction:

```text
Append supervisor-visible status lines to <absolute-firstmate-home>/state/<task-id>.status.
Use only these prefixes for status changes: working:, needs-decision:, blocked:, paused:, done:, failed:.
Use paused: only for a deliberate known external wait that should be rechecked later, never for a blocker that needs firstmate to act.
Before doing substantive work, append "working: Codex Desktop thread started".
```

Verify the return channel before treating the thread as supervised:

- `read_thread` shows the worker attempted the status write.
- The local `state/<task-id>.status` file contains the expected line.
- If available, the transcript includes a file-change entry for that status file.

If the thread cannot write the status file, keep it as a visible companion thread only.
Do not claim it is a complete Firstmate backend.

## Observe And Reconcile

Use `read_thread` for thread truth.
Use `list_threads` only to find or recover a visible thread id, not as a replacement for reading the transcript.

For Firstmate reconciliation, prefer concrete evidence:

- thread id and project
- current Desktop-owned cwd
- branch name
- last meaningful thread state
- latest status file line
- PR URL when one exists

Avoid repeating long transcripts into Firstmate docs or PR bodies.
Summarize only the host-tool calls, the status-file result, and the archive result.
When reporting a Desktop-thread result to the captain, translate status prefixes and return-channel evidence through `AGENTS.md` section 9.
The adapter leases an isolated worktree, creates a durable Codex thread, submits the marked brief, and records the thread id in task metadata.
The brief must retain the normal absolute `state/<id>.status` instructions.
Treat spawn as successful only after the adapter observes a new valid lifecycle line from that thread.

## Archive
## Supervision

Archive through the Desktop host tool: `archive` when that is the exposed primitive, or `set_thread_archived(threadId=<id>, archived=true)` when that is the exposed tool name.
Archiving can remove the thread from normal sidebar/project views, but it should not erase the transcript or landed work.
Use `fm-send.sh`, `fm-peek.sh`, and `fm-crew-state.sh`.
An active follow-up is steered into the current turn; an idle follow-up starts a new turn.
Busy and idle are exact app-server lifecycle states.
Treat a missing or unreadable thread as unknown or dead through the shared backend classifier, never as idle.

For companion threads, archive the thread and report where the durable work landed.
If there is a real Firstmate task record, leave teardown decisions to the normal Firstmate task flow instead of this skill.
## Recovery and retirement

## Failure Signals
The per-home app-server restarts automatically on the next create, send, read, lifecycle, interruption, or archive operation.
The durable thread id remains the endpoint authority across that restart.
Never invent a replacement thread while recorded metadata still identifies a readable endpoint.

- Missing Desktop project: ask the human to add the target project in Codex Desktop, or use a normal backend.
- Missing host tools: do not simulate them with shell files; use a terminal backend instead.
- Status file not updated: treat the thread as unsupervised until the return channel is proven.
- Worker editing the saved project checkout instead of its Desktop cwd: stop and decide whether to salvage the branch before continuing.
- Production `codex-app` backend request: read `docs/codex-app-backend.md` and do not invent a local adapter.
Retire through `fm-teardown.sh`.
It validates the exact metadata binding, interrupts an active turn, archives the thread, and then returns the worktree.
If archive fails, preserve the metadata and lease for recovery.
9 changes: 6 additions & 3 deletions .agents/skills/harness-adapters/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: harness-adapters
description: Agent-only reference for firstmate harness operations. Use before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. Contains verified facts for claude, codex, opencode, pi, pi-signed, grok, and kimi.
description: Agent-only reference for firstmate harness operations. Use before spawning or recovering a crewmate or secondmate, handling a trust dialog, sending a harness-specific skill invocation, interrupting or exiting an agent, resuming an exited agent, or verifying a new harness adapter. Contains verified facts for claude, codex, opencode, pi, pi-signed, grok, kimi, and the Hermes primary adapter.
user-invocable: false
metadata:
internal: true
Expand Down Expand Up @@ -39,7 +39,7 @@ If the captain asks for a new harness, propose verifying it first: spawn a trivi

## Detection

`bin/fm-harness.sh` prints firstmate's own harness, using verified env markers first and then process ancestry.
`bin/fm-harness.sh` prints firstmate's own harness, using verified env markers first and then process ancestry. Hermes is recognized only as the persistent primary path; its trusted normal signature is `hermes -p firstmate --tui`, while one-shot Hermes workers never own the primary lock.
Within the Pi family, only the exact launch-boundary marker `FM_PI_HARNESS=pi-signed` alongside `PI_CODING_AGENT=true` selects the signed identity; unmarked shared launcher ancestry remains `pi`.
`bin/fm-harness.sh crew` resolves the effective crewmate harness from `config/crew-harness` (absent or `default` -> own).
`bin/fm-harness.sh secondmate` resolves the secondmate-launch harness through the chain `config/secondmate-harness` -> `config/crew-harness` -> own, so an unset `config/secondmate-harness` matches the crew harness.
Expand All @@ -62,11 +62,14 @@ The exact hook files, commands, scoping rules, and fail-open tradeoffs are owned
`docs/verification/supervision.md` "Turn-end guard" owns active validation evidence.
When changing any primary turn-end hook, validate the real harness behavior in a scratch project or throwaway home before trusting it, then update that doc and the relevant concise fact below.

Hermes Agent 0.20.0 is primary-only. Its tracked project plugin uses `pre_tool_call`, `post_llm_call`, and `on_session_finalize`; it blocks `delegate_task`, remains inert outside the real persistent primary scope, and directs visible dispatch through `bin/fm-spawn.sh`. Hermes supervision uses its managed `terminal(background=true, notify_on_complete=true)` registry, never shell `&`. Crewmate launches continue to resolve through the configured `config/backend=herdr` and optional `config/herdr-presentation-spaces` contract; Hermes has no direct worker path or hidden fallback.

## Primary pre-arm (PreToolUse) seatbelt

The primary integrations for `claude`, `codex`, `opencode`, `pi`, `pi-signed`, and `grok` also have wired PreToolUse-equivalent hooks that deny a watcher-arm anti-pattern (shell `&`, truncating pipe, bundling, broad `pkill -f fm-watch`) before it runs.
The primary integrations for `claude`, `codex`, `opencode`, `pi`, `pi-signed`, `grok`, and Hermes also have wired PreToolUse-equivalent hooks that deny a watcher-arm anti-pattern (shell `&`, truncating pipe, bundling, broad `pkill -f fm-watch`) before it runs.
`claude` and `codex` block directly through PreToolUse hooks; `grok` blocks the same way but requires every `$VAR` reference in its hook `command` string to carry an inline `:-default` or it fails to launch the hook entirely.
`opencode`, `pi`, and `pi-signed` block by throwing from `tool.execute.before` / returning `{block: true}` from `tool_call`.
Hermes blocks by mapping `pre_tool_call` to `bin/fm-arm-pretool-check.sh` in the tracked `firstmate-primary` plugin.
The exact hook files, commands, output-shaping quirks (Claude Code only honors the deny when stdout is empty), and validation transcripts are owned by `docs/arm-pretool-check.md`.
When changing any watcher-arm PreToolUse hook, validate the real harness behavior in a scratch project before trusting it, then update that doc.
## Primary delegation-shape guard
Expand Down
17 changes: 15 additions & 2 deletions .agents/skills/updatefirstmate/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: updatefirstmate
description: >-
Self-update a running firstmate and its secondmates to the latest from origin.
Use when the captain invokes /updatefirstmate (e.g. "/updatefirstmate", "update firstmate", "pull the latest firstmate").
Fast-forwards this firstmate repo's default branch and every local or remote secondmate through its guarded update path (never forced, never disruptive), then re-reads AGENTS.md and nudges each updated secondmate to do the same, so the whole tree runs the latest bin/ and instructions.
When private-upstream configuration is present, safely integrates and validates the public upstream in a disposable clone before publishing private origin/main.
Then fast-forwards this firstmate repo's default branch and every local or remote secondmate through its guarded update path (never forced, never disruptive), then re-reads AGENTS.md and nudges each updated secondmate to do the same, so the whole tree runs the latest bin/ and instructions.
user-invocable: true
metadata:
internal: true
Expand All @@ -16,6 +17,11 @@ Firstmate is its own repo, behind the same no-mistakes gate as any project, so n
Only `AGENTS.md`, `bin/`, and `.agents/skills/` are a running firstmate instruction surface; public `skills/` is installer-facing and is not loaded by firstmate.
This skill performs that pull for the running main firstmate and every secondmate, without disturbing any in-flight work.

An installation may opt into a private distribution with local `config/private-upstream`.
In that mode the updater first integrates the declared public branch into private `origin/main` in a disposable clone, validates the result, and publishes only to the declared private URL.
The running copy remains untouched until that publication succeeds, then the normal guarded origin fast-forward updates it and its secondmates.
The operator setup and current config contract are in [`docs/configuration.md`](../../../docs/configuration.md#private-upstream-distribution-configprivate-upstream), while `bin/fm-private-update.sh` owns exact parsing and mechanics.

The update is **fast-forward only** - the same sanctioned self-write as the fleet sync firstmate already runs.
For a remote route, it updates the configured Firstmate code root on that host from its own origin, then guardedly fast-forwards the persistent home to that code-root commit.
It never forces, never creates a merge commit, never stashes, and advances a target only on a clean fast-forward; anything dirty, diverged, offline, or on the wrong branch is skipped and reported.
Expand All @@ -28,7 +34,8 @@ This touches only the firstmate repo and its own worktrees, never anything under
```sh
bin/fm-update.sh
```
It fast-forwards this firstmate repo's default branch from origin, then updates every registered local or remote secondmate home through its placement-specific guarded path.
Without private-upstream configuration, it fast-forwards this firstmate repo's default branch from origin exactly as before, then updates every registered local or remote secondmate home through its placement-specific guarded path.
With private-upstream configuration, it first prints a `private-upstream:` outcome for the isolated integration and private publication, then fast-forwards this firstmate repo and every registered local or remote secondmate home from private origin the same way.
It prints one status line per target (`updated <old>..<new>` / `already current` / `skipped: <reason>`), followed by two action lines that tell you exactly what to do next:
- `reread-firstmate: yes|no`
- `nudge-secondmates: fm-<id>...|none`
Expand All @@ -50,13 +57,19 @@ This touches only the firstmate repo and its own worktrees, never anything under
4. **Report to the captain in plain outcomes.**
Summarize what landed under `AGENTS.md` section 9 without firstmate's internal vocabulary: which parts of the fleet are now on the latest, and which were left as-is and why.
For example: "Captain, firstmate and both second mates are now on the latest."
In private mode, say whether the public update was already included or was validated and published to private main before the fleet advanced.
If the private integration stopped, name the reason and the reported evidence path, and make clear that private main and the running fleet were left unchanged.
Surface any skipped target whose reason needs the captain's attention - for instance a home with its own un-landed changes (diverged) or local edits (dirty), which were left untouched on purpose.

## Safety

- **Fast-forward only.**
A target that has diverged, is dirty, is offline, or is on a non-default branch is skipped and reported, never forced or stashed.
Nothing with unlanded work is ever discarded - this is prime directive #3.
- **Private integration is isolated.**
Merge conflicts, validation failures, divergence, and push failures preserve a disposable evidence clone and stop before the running checkout or secondmates can advance.
- **Public remotes are read-only.**
Private mode validates the declared private origin fetch and push URL, the declared public upstream fetch URL, and the public remote's disabled push sentinel before performing network work.
- **Only the firstmate repo and its worktrees** are touched, never `projects/`.
It is the same sanctioned self-write as the fleet sync.
- **Secondmates are never disrupted.**
Expand Down
43 changes: 0 additions & 43 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -320,49 +320,6 @@ jobs:
path: ${{ runner.temp }}/fm-test/fm-test-timing-aggregate.json
if-no-files-found: warn

macos-stock-bash:
name: Stock macOS Bash snapshot compatibility
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- name: Run snapshot consumers with stock Bash
shell: /bin/bash {0}
env:
PATH: /bin:/usr/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/homebrew/bin
run: |
set -eu
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
command -v jq >/dev/null || { echo "::error::jq is required"; exit 1; }

shell_inventory="$RUNNER_TEMP/fm-shell-inventory"
bin/fm-lint.sh --list-files > "$shell_inventory"
parse_fail=0
while IFS= read -r f; do
/bin/bash -n "$f" || { echo "::error::stock macOS Bash 3.2 failed to parse $f"; parse_fail=1; }
done < "$shell_inventory"
[ "$parse_fail" -eq 0 ] || { echo "::error::stock macOS Bash 3.2 parse sweep failed"; exit 1; }

snapshot_output=$(/bin/bash tests/fm-fleet-snapshot-view.test.sh)
printf '%s\n' "$snapshot_output"
snapshot_count=$(printf '%s\n' "$snapshot_output" | grep -c '^ok - ')
[ "$snapshot_count" -eq 15 ] || {
echo "::error::expected 15 snapshot/fleet-view tests, got $snapshot_count"
exit 1
}

bearings_output=$(/bin/bash tests/fm-bearings-snapshot.test.sh)
printf '%s\n' "$bearings_output"
bearings_count=$(printf '%s\n' "$bearings_output" | grep -c '^ok - ')
[ "$bearings_count" -eq 41 ] || {
echo "::error::expected 41 Bearings tests, got $bearings_count"
exit 1
}

invariants:
name: Repo invariants
runs-on: ubuntu-latest
Expand Down
Loading