Skip to content
Merged
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
6 changes: 3 additions & 3 deletions .agents/skills/harness-adapters/references/common/dispatch.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ When the opt-in `bin/fm-dispatch-resolve.sh` is on, its `clear` answer already n

[`secondmate-provisioning`](../secondmate-provisioning/SKILL.md) owns inherited harness defaults and routing configuration.

## Skill advice at intake
## Skill selection at launch

Apply every existing mandatory explicit/named and safety trigger before considering optional advice.
Follow [advisory skill selection](../../../docs/configuration.md#advisory-skill-selection) at intake and on material intent changes; it owns permitted input, worker overlays, privacy, role coverage, and supported limits.
Apply every existing mandatory explicit/named and safety trigger before the launch's skill selection.
Follow [Worker skill selection](../../../docs/configuration.md#worker-skill-selection) for ship and scout launches; it owns input, brief transport, privacy, provider policy, and recorded outcomes.


## Owners
Expand Down
2 changes: 1 addition & 1 deletion .agents/skills/operational-home-layout/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ README.md public overview and development notes
.claude/mods/ Claude Code mods (function-hooks plugins), committed; Calm's module may load through CLAUDE_CODE_ENABLE_FUNCTION_HOOKS or tengu_plugin_hooks_modules, but activates only under the exact opt-in docs/calm.md owns and is otherwise a complete no-op
skills/ standalone public installer-facing skills, committed; not loaded by firstmate
bin/ helper scripts, committed; read each script's header before first use
.env optional Relay pairing token (presence-gates section 14), mail-plane credentials (schema: docs/configuration.md "Mail plane"), and TypeSafe key TYPESAFE_API_KEY, kept once in the primary home and read by its secondmate homes through their parent record (docs/configuration.md "Typed dispatch resolution" and "Advisory skill selection"); LOCAL, gitignored
.env optional Relay pairing token (presence-gates section 14), mail-plane credentials (schema: docs/configuration.md "Mail plane"), TypeSafe key TYPESAFE_API_KEY, kept once in the primary home and read by its secondmate homes through their parent record, and OpenRouter fallback key OPENROUTER_API_KEY (docs/configuration.md "Typed dispatch resolution" and "Worker skill selection"); LOCAL, gitignored
config/crew-harness crewmate harness override; LOCAL, gitignored; absent or "default" = same as firstmate. Inherited as the literal file: a concrete primary adapter value also controls a secondmate home's own crewmates (section 4)
config/claude-permission-mode optional one-token permission posture for every Claude worker launch: absent or "bypass" keeps --dangerously-skip-permissions, "auto" launches with --permission-mode auto; LOCAL, gitignored; inherited by secondmate homes; see docs/configuration.md "Claude permission mode"
config/claude-launcher optional Claude worker launcher; see docs/configuration.md "Claude launcher" for selection, proxy requirements, and inheritance
Expand Down
41 changes: 39 additions & 2 deletions bin/fm-brief-heading-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
# Usage: . bin/fm-brief-heading-lib.sh
#
# This file is the single owner of brief-section parsing for intent,
# validation, dispatch resolution, and advisory skill selection.
# Every consumer shares parsing semantics; resolver-only privacy preprocessing
# validation, dispatch resolution, and worker skill selection.
# Every consumer shares parsing semantics; never-send privacy preprocessing
# is owned by docs/configuration.md "Never-send list".

# Parse an exact ATX heading outside fenced blocks. Body mode prints through
Expand Down Expand Up @@ -92,3 +92,40 @@ fm_brief_task_heading_present() { # <file> <heading>
printf '%s\n' "$task" | fm_brief_heading_parse - "$2" present >/dev/null
}

# Print the words of every provenance-marked line in a legacy `# Task` body.
# The marker is read the way bin/fm-brief-heading-lib.sh reads a heading: a
# line inside a ``` or ~~~ fenced block, or indented four spaces or a tab as an
# indented example, is never a marked line, so a fenced `Captain:` sample cannot
# pass the provenance gate as the ship contract's intent (issue 3608).
fm_brief_marked_captain_words() { # <task-body>
printf '%s\n' "$1" | awk '
{
scan = $0
spaces = 0
while (spaces < 3 && substr(scan, 1, 1) == " ") {
scan = substr(scan, 2)
spaces++
}
marker = substr(scan, 1, 1)
marker_len = 0
if (marker == "`" || marker == "~") {
while (substr(scan, marker_len + 1, 1) == marker) marker_len++
}
if (marker_len >= 3) {
if (!fenced) {
fenced = 1
fence_marker = marker
fence_len = marker_len
} else if (marker == fence_marker && marker_len >= fence_len && substr(scan, marker_len + 1) ~ /^[[:space:]]*$/) {
fenced = 0
}
next
}
if (fenced || substr(scan, 1, 1) ~ /^[ \t]$/) next
if (match(scan, /^(\[captain\]|Captain('\''s (words|ask|intent))?:)[[:space:]]*/)) {
words = substr(scan, RLENGTH + 1)
if (words ~ /[^[:space:]]/) print words
}
}
'
}
82 changes: 6 additions & 76 deletions bin/fm-dispatch-resolve.sh
Original file line number Diff line number Diff line change
Expand Up @@ -310,63 +310,13 @@ never_send_off() {
}

# Snapshot and validate privacy policy before reading any outgoing brief text.
NEVER_SEND_LIST='' MARKED_SECTIONS=0
NEVER_SEND_LIST=''
never_send_load() {
local value n=0
fm_typesafe_policy_inspect "$NEVER_SEND_PATH" \
fm_typesafe_policy_marked_sections "$NEVER_SEND_PATH" \
|| never_send_off "$FM_TYPESAFE_WITHHELD_REASON"
[ -e "$NEVER_SEND_PATH" ] || [ -L "$NEVER_SEND_PATH" ] || return 0
NEVER_SEND_LIST=$(jq -Rr 'gsub("\\s+"; " ")' "$NEVER_SEND_PATH" 2>/dev/null) \
|| never_send_off "could not read $NEVER_SEND_PATH"
while IFS= read -r value; do
n=$((n + 1))
value=${value# }
value=${value% }
case "$value" in
'# dispatch-never-send marked-sections') MARKED_SECTIONS=1 ;;
'#'*)
case "$(printf '%s' "${value#'#'}" | tr '[:upper:]' '[:lower:]')" in
dispatch-never-send*|' dispatch-never-send'*)
never_send_off "invalid privacy directive in $NEVER_SEND_PATH line $n" ;;
esac
;;
esac
done <<<"$NEVER_SEND_LIST"
}

# Markers are interpreted on the original brief, even inside Markdown fences,
# so protected headings cannot change extraction or cause a whole-brief fallback.
never_send_brief() {
local rc=0
grep -qiE -e '<!--[[:space:]]*dispatch-never-send' "$BRIEF" 2>/dev/null || rc=$?
case "$rc" in
0) [ "$MARKED_SECTIONS" -eq 1 ] \
|| never_send_off "never-send markers need the marked-sections directive" ;;
1) cp "$BRIEF" "$SEND_TEXT" || never_send_off "could not read the brief"
return ;;
*) never_send_off "could not read the brief" ;;
esac
awk '
{
marker = $0
sub(/^[[:space:]]+/, "", marker)
sub(/[[:space:]]+$/, "", marker)
if (marker == "<!-- dispatch-never-send:start -->") {
if (hidden) exit 1
hidden = 1
next
}
if (marker == "<!-- dispatch-never-send:end -->") {
if (!hidden) exit 1
hidden = 0
next
}
if (tolower($0) ~ /<!--[[:space:]]*dispatch-never-send/) exit 1
if (!hidden) print
}
END { if (hidden) exit 1 }
' "$BRIEF" > "$SEND_TEXT" 2>/dev/null \
|| never_send_off "invalid never-send markers or unreadable brief"
}

# Checks every string the request carries. grep stderr can echo the pattern.
Expand All @@ -391,31 +341,11 @@ never_send_check() {
done <<<"$NEVER_SEND_LIST"
}

# Send Jev only the task-specific sections bin/fm-brief.sh scaffolds, plus a
# scout tag from the scout contract line; the rest of a scaffolded brief is
# standard boilerplate whose safety language reads as high stakes on every task.
# A brief with neither section goes whole. Ship delivery mode is deliberately
# not sent: live runs showed it pushing routine ship briefs to the top tier.
brief_kind() {
if grep -qxF 'This is a SCOUT task: the deliverable is a written report, not a PR.' "$SEND_TEXT"; then
printf 'Brief kind: scout (report only)\n\n'
fi
}
task_sections() {
local heading
for heading in "## Captain's intent" "## Firstmate spec"; do
fm_brief_task_heading_present "$SEND_TEXT" "$heading" || continue
printf '%s\n%s\n\n' "$heading" "$(fm_brief_task_heading_body "$SEND_TEXT" "$heading")"
done
}
# fm_typesafe_brief_task owns marker removal and the task-section extraction
# shared with worker skill selection.
never_send_load
never_send_brief
SECTIONS=$(task_sections)
if [ -n "$SECTIONS" ]; then
{ brief_kind; printf '%s\n' "$SECTIONS"; } > "$TASK_TEXT" || die "could not read brief: $BRIEF"
else
cp "$SEND_TEXT" "$TASK_TEXT" || die "could not read brief: $BRIEF"
fi
fm_typesafe_brief_task "$BRIEF" "$NEVER_SEND_PATH" "$TASK_TEXT" \
|| never_send_off "$FM_TYPESAFE_WITHHELD_REASON"
command -v curl >/dev/null 2>&1 || emit_error "curl not installed"
REQUEST=$(jq -n --rawfile brief "$TASK_TEXT" --arg project "$PROJECT" --arg model "$TS_MODEL" \
--arg none_criterion "$DEFAULT_WHEN" --slurpfile rules "$RULES" '
Expand Down
38 changes: 0 additions & 38 deletions bin/fm-dod-lib.sh
Original file line number Diff line number Diff line change
Expand Up @@ -196,44 +196,6 @@ fm_brief_task_placeholders_present() { # <file>
return 1
}

# Print the words of every provenance-marked line in a legacy `# Task` body.
# The marker is read the way bin/fm-brief-heading-lib.sh reads a heading: a
# line inside a ``` or ~~~ fenced block, or indented four spaces or a tab as an
# indented example, is never a marked line, so a fenced `Captain:` sample cannot
# pass the provenance gate as the ship contract's intent (issue 3608).
fm_brief_marked_captain_words() { # <task-body>
printf '%s\n' "$1" | awk '
{
scan = $0
spaces = 0
while (spaces < 3 && substr(scan, 1, 1) == " ") {
scan = substr(scan, 2)
spaces++
}
marker = substr(scan, 1, 1)
marker_len = 0
if (marker == "`" || marker == "~") {
while (substr(scan, marker_len + 1, 1) == marker) marker_len++
}
if (marker_len >= 3) {
if (!fenced) {
fenced = 1
fence_marker = marker
fence_len = marker_len
} else if (marker == fence_marker && marker_len >= fence_len && substr(scan, marker_len + 1) ~ /^[[:space:]]*$/) {
fenced = 0
}
next
}
if (fenced || substr(scan, 1, 1) ~ /^[ \t]$/) next
if (match(scan, /^(\[captain\]|Captain('\''s (words|ask|intent))?:)[[:space:]]*/)) {
words = substr(scan, RLENGTH + 1)
if (words ~ /[^[:space:]]/) print words
}
}
'
}

fm_brief_intent_overlay() { # <captain-intent>
cat <<'EOF'

Expand Down
24 changes: 0 additions & 24 deletions bin/fm-skill-catalog.jq

This file was deleted.

Loading
Loading