Skip to content
Open
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
7 changes: 7 additions & 0 deletions .opencode/plugins/fm-primary-turnend-guard.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ function runProcess(command, args, input = "") {
});
child.on("error", () => resolve({ code: 0, stdout: "", stderr: "" }));
child.on("close", (code) => resolve({ code: code ?? 0, stdout, stderr }));
// A guard child may exit before it drains stdin (bin/fm-turnend-guard.sh
// rejects bad usage with exit 2 before its `cat`, and any child can lose
// the race between spawn and this write). The payload write then fails
// EPIPE, and an unhandled stream "error" event would take the whole
// OpenCode host process down. The child's exit code and stderr carry the
// guard verdict, so a payload the child never wanted is safe to drop.
child.stdin.on("error", () => {});
child.stdin.end(input);
});
}
Expand Down
4 changes: 4 additions & 0 deletions .pi/extensions/fm-primary-turnend-guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,10 @@ function runGuard(): Promise<{ code: number; stderr: string }> {
});
child.on("error", () => resolveResult({ code: 0, stderr: "" }));
child.on("close", (code) => resolveResult({ code: code ?? 0, stderr }));
// Same EPIPE tolerance the OpenCode plugin needs: a guard child that exits
// before draining stdin turns this write into an unhandled stream "error"
// event, which would kill the Pi host. The verdict is the exit code.
child.stdin.on("error", () => {});
child.stdin.end('{"stop_hook_active":false}');
});
}
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ data/ personal fleet records; LOCAL, gitignored as a whole
secondmates.md local and remote secondmate routing table; firstmate-private, maintained by the secondmate seed helpers (section 6)
<id>/brief.md per-task crewmate brief, or per-secondmate charter brief when kind=secondmate
<id>/report.md scout task deliverable, written by the crewmate; survives teardown
decisions/*.md decision records; survives teardown
projects/ cloned repos; gitignored; read-only except under hard rule 1's concrete captain-approved project operation exception
state/ runtime records and signals; gitignored
<id>.status appended by crewmates: "<state>: <note>" wake-event lines, not current-state truth
Expand Down
104 changes: 104 additions & 0 deletions bin/fm-hindsight-recall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env bash
# fm-hindsight-recall.sh - query Hindsight memory bank for Firstmate on demand.
#
# Semantic policy:
# - On-demand recall for firstmate to search past investigations and decisions.
# - Never injected automatically into prompt or context.
# - Compact, greppable output format by default; JSON supported via --json.
#
# Usage:
# fm-hindsight-recall.sh <query> [--bank <bank>] [--url <url>] [--limit <n>] [--json]

set -u

HINDSIGHT_URL="${FM_HINDSIGHT_URL:-${HINDSIGHT_URL:-http://hindsight-1:8888}}"
HINDSIGHT_BANK="${FM_HINDSIGHT_BANK:-${HINDSIGHT_BANK:-firstmate}}"
QUERY=""
FORMAT_JSON=0
MAX_TOKENS=4096

usage() {
cat << 'EOF'
Usage:
fm-hindsight-recall.sh <query> [--bank <bank>] [--url <url>] [--json]

Options:
--bank <bank> Hindsight bank ID (default: firstmate)
--url <url> Hindsight base URL (default: http://hindsight-1:8888)
--json Output raw JSON response
-h, --help Show this help message
EOF
}

while [ $# -gt 0 ]; do
case "$1" in
--json)
FORMAT_JSON=1
shift
;;
--bank)
HINDSIGHT_BANK=$2
shift 2
;;
--url)
HINDSIGHT_URL=$2
shift 2
;;
-h|--help)
usage
exit 0
;;
-*)
printf 'unknown option: %s\n' "$1" >&2
usage >&2
exit 2
;;
*)
if [ -z "$QUERY" ]; then
QUERY=$1
else
QUERY="$QUERY $1"
fi
shift
;;
esac
done

if [ -z "$QUERY" ]; then
usage >&2
exit 2
fi

payload=$(jq -n \
--arg q "$QUERY" \
--argjson max_tokens "$MAX_TOKENS" \
'{
query: $q,
max_tokens: $max_tokens
}')

url="$HINDSIGHT_URL/v1/default/banks/$HINDSIGHT_BANK/memories/recall"
resp=$(curl -s -S --connect-timeout 3 --max-time 60 \
-X POST "$url" \
-H "Content-Type: application/json" \
-d "$payload" 2>&1) || rc=$?

if [ "${rc:-0}" -ne 0 ]; then
printf 'error: recall request failed (exit %s): %s\n' "$rc" "$resp" >&2
exit 1
fi

if [ "$FORMAT_JSON" -eq 1 ]; then
printf '%s\n' "$resp" | jq .
exit 0
fi

# Format output compactly: [<document_id>] (<type>) <text>
printf '%s\n' "$resp" | jq -r '
if (.results | length) == 0 then
"no memories found for query"
else
.results[] |
"[" + (.document_id // "observation") + "] (" + .type + ") " + (.text | gsub("\n"; " "))
end
'
Loading
Loading