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
29 changes: 21 additions & 8 deletions bin/fm-watch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1663,15 +1663,22 @@ surface_nonterminal_stale() { # <window> <hash>
# jev_triage_enabled refuses while the away daemon or the away-posture record
# exists, and jev_triage_task_evidence refuses a secondmate, a task with any
# open keyed decision, a declared clearing time that has passed, an endpoint
# whose agent is not proven alive, and a task with no recorded pull request.
# whose agent state is anything but alive or dead, and a task with no recorded
# pull request. A dead agent beside an existing endpoint is offered because that
# is the state pause_state_class routes to the pause recheck for an ordinary
# crew, so refusing it left that recheck with no task it could ever offer; the
# evidence tells the model the agent exited, and the question keeps such a wait
# routine only when it is the merge of the open pull request.
#
# The recorded pull request (pr= in the task's metadata, which only firstmate
# records after reading the worker's ready report) is what shows firstmate
# already knows the wait, so a paused task without one is never offered and
# surfaces exactly as before. The deterministic read comes first and settles
# what it can: the task is offered only while bin/fm-pr-state.sh reads that pull
# request as open with no blocker reported, so a merged, closed, conflicting,
# failing, or unreadable one is delivered without asking.
# failing, or unreadable one is delivered without asking. A repository that
# reports no checks at all is not a blocker: only a check that reported and is
# failing or pending is.
#
# ponytail: one consecutive-absorb counter per subject bounds the model the way
# PAUSE_RESURFACE_SECS bounds the deterministic absorbs - after
Expand All @@ -1681,8 +1688,9 @@ JEV_TRIAGE_MAX_STREAK=6
JEV_TRIAGE_PR_TIMEOUT=30
FM_PR_STATE_BIN="${FM_PR_STATE_BIN:-$SCRIPT_DIR/fm-pr-state.sh}"
# shellcheck disable=SC2016 # The backticks are literal markup in the question text.
JEV_TRIAGE_INSTRUCTIONS='A monitoring reminder in `wake` is about to interrupt the supervisor of a fleet of software workers. The supervisor has already seen every status event listed in `wake`, so nothing in it is news. Decide from the words of the evidence what is being waited on. Choose `routine` only when the wait will clear without any action from the supervisor or another person, apart from the already reported merge of an open pull request. When the worker is waiting on a person or on the supervisor to choose, answer, review, approve, or unblock something, or it stopped after a failure, the reminder is how the supervisor remembers that someone still owes the worker a reply, so choose `needs_firstmate`. Choose `needs_firstmate` whenever you are unsure.'
JEV_TRIAGE_CRITERIA='{"needs_firstmate":"The newest status event shows the worker waiting on a person or on the supervisor: to choose between options, answer a question, review or accept finished work, supply or repair a credential or access, or unblock it; or the worker stopped after a failure or an error; or a diagnostic reports anything other than a read that timed out; or it is not clear what the wait is for.","routine":"The newest status event declares a wait on an automatic external event that clears without the supervisor: an open pull request with nothing failing that only awaits its already reported merge, a running CI job, a rate limit or quota reset, a scheduled window, or an upstream release; or one transient read timeout that the monitor retries on its own."}'
JEV_TRIAGE_INSTRUCTIONS='A monitoring reminder in `wake` is about to interrupt the supervisor of a fleet of software workers. The supervisor has already seen every status event listed in `wake`, so nothing in it is news. Decide from the words of the evidence what is being waited on. Choose `routine` only when the wait will clear without any action from the supervisor or another person, apart from the already reported merge of an open pull request. A worker that says only that its open pull request is waiting to be merged, or waiting for the merge word or merge approval of the supervisor, the captain, or anyone else, is waiting on that already reported merge, so that is `routine`. When the worker is waiting on a person or on the supervisor to choose, answer, review, approve, or unblock anything other than that merge, or it stopped after a failure, the reminder is how the supervisor remembers that someone still owes the worker a reply, so choose `needs_firstmate`. When `worker_agent` is `exited`, the worker cannot resume by itself: that changes nothing for a pull request that only awaits its merge, which needs nothing more from the worker and stays `routine`, but every other wait is then `needs_firstmate`. Choose `needs_firstmate` whenever you are unsure.'
# shellcheck disable=SC2016 # As above.
JEV_TRIAGE_CRITERIA='{"needs_firstmate":"The newest status event shows the worker waiting on a person or on the supervisor for anything other than the merge of its open pull request: to choose between options, answer a question, review or accept finished work, supply or repair a credential or access, or unblock it; or the worker stopped after a failure or an error; or `worker_agent` is `exited` and the wait is for anything other than the merge of its open pull request; or a diagnostic reports anything other than a read that timed out; or it is not clear what the wait is for.","routine":"The newest status event declares an open pull request with nothing failing that only awaits its already reported merge, whoever gives the merge word and whether `worker_agent` is `running` or `exited`; or, while `worker_agent` is `running`, a wait on an automatic external event that clears without the supervisor: a running CI job, a rate limit or quota reset, a scheduled window, or an upstream release; or one transient read timeout that the monitor retries on its own."}'
JEV_TRIAGE_TASK_EVIDENCE=

jev_triage_enabled() {
Expand All @@ -1694,7 +1702,7 @@ jev_triage_enabled() {
# 0 when <task> may be offered to Jev, leaving its evidence object in
# JEV_TRIAGE_TASK_EVIDENCE; 1 for every task the model must never decide.
jev_triage_task_evidence() { # <task>
local task=$1 meta statusf last until w pr pr_out
local task=$1 meta statusf last until w pr pr_out agent
JEV_TRIAGE_TASK_EVIDENCE=
case "$task" in ''|*[!A-Za-z0-9._-]*) return 1 ;; esac
meta="$STATE/$task.meta"
Expand All @@ -1711,14 +1719,19 @@ jev_triage_task_evidence() { # <task>
[ -n "$pr" ] || return 1
w=$(fm_backend_target_of_meta "$meta")
[ -n "$w" ] || return 1
[ "$(fm_backend_agent_state "$(fm_backend_of_meta "$meta")" "$w" 2>/dev/null || true)" = alive ] || return 1
case "$(fm_backend_agent_state "$(fm_backend_of_meta "$meta")" "$w" 2>/dev/null || true)" in
alive) agent='running' ;;
dead) agent='exited' ;;
*) return 1 ;;
esac
pr_out=$(fm_run_timed "$JEV_TRIAGE_PR_TIMEOUT" "$FM_PR_STATE_BIN" "$pr" 2>/dev/null) || return 1
if [ -n "$pr_out" ] && printf '%s\n' "$pr_out" | grep -Eqv '^(CHECKS: |MERGEABILITY: unknown$)'; then
return 1
fi
JEV_TRIAGE_TASK_EVIDENCE=$(tail -n 6 "$statusf" 2>/dev/null | cut -c 1-400 \
| jq -Rsc --arg task "$task" --arg pr 'open; no blocker reported' \
'{task: $task, pull_request: $pr, recent_status_events: (split("\n") | map(select(length > 0)))}' \
| jq -Rsc --arg task "$task" --arg pr 'open; no blocker reported' --arg agent "$agent" \
'{task: $task, pull_request: $pr, worker_agent: $agent,
recent_status_events: (split("\n") | map(select(length > 0)))}' \
2>/dev/null) || return 1
[ -n "$JEV_TRIAGE_TASK_EVIDENCE" ]
}
Expand Down
8 changes: 5 additions & 3 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,16 +231,18 @@ Only two enumerated wake classes are ever offered, each wired at its own point i

No signal is ever offered, so every new status event and every bare turn-end surfaces as it does without the feature.
A paused task is eligible only when a pull request is recorded for the task (`pr=` in its metadata) and is still open with no blocker reported; a paused task with no recorded pull request is never offered and surfaces exactly as it does without the feature.
The question asks what the declared wait is for: a wait on an automatic external event may read as routine, while a worker waiting on a person or stopped after a failure is delivered.
The question asks what the declared wait is for: an open pull request that only awaits its merge word, whoever gives it, or a wait on an automatic external event may read as routine, while a worker waiting on a person for anything else or stopped after a failure is delivered.

Everything else is refused in code before any call and never depends on the model's answer.
That covers captain notes, every new status event, every turn-end, merge and PR-ready outcomes, process-event and Relay wakes, every other check, any task with an open keyed decision, a secondmate, a declared clearing time that has passed, and every wake while the away-mode daemon or the away-posture record exists.
An endpoint whose agent is not proven alive is never routine, so a dead, missing, ambiguous, unreadable, or unverified endpoint is always delivered.
An endpoint whose agent state cannot be established is never routine, so a missing, ambiguous, unreadable, or unverified endpoint is always delivered.
A worker whose agent is running, or has exited while its endpoint remains, is offered, and the model is told which; for an exited agent the question keeps only a pull request awaiting its merge routine, since that worker cannot resume any other wait by itself.
The watcher first reads the recorded pull request through `bin/fm-pr-state.sh` and offers the wake only while that read reports the pull request open with no blocker, which is all the model is told about it; a merged, closed, blocked, or unreadable pull request delivers the wake without asking, so an absorbed pause recheck is still presented on its normal cadence once the wait has actually changed.
A repository that reports no checks at all is not a blocker, while a draft, a conflict, a requested change, and a required check that is failing or pending each are.
A routine answer advances the same recheck cadence a delivered wake would, so the wait is read again every `FM_PAUSE_RESURFACE_SECS` rather than silenced.
One task may receive six routine answers in a row before the next wake is delivered unasked and the count restarts, so the model can never mute a wait indefinitely.

Each call sends the wake's class and reason line, the task id, its last six status lines cut to 400 characters each, and its pull request read, or for a contributions check its diagnostic lines; it sends no brief, pane content, or quota.
Each call sends the wake's class and reason line, the task id, its last six status lines cut to 400 characters each, its pull request read, and whether its agent is running or has exited, or for a contributions check its diagnostic lines; it sends no brief, pane content, or quota.
`bin/fm-jev-lib.sh` owns the request, the answer validation, and the key handling shared with typed dispatch resolution, `bin/fm-watch.sh`'s `jev_triage_routine` header owns the exact eligibility gates, and [`verification/jev-wake-triage.md`](verification/jev-wake-triage.md) records the live evidence.

## House-rules check (.env TYPESAFE_API_KEY, config/house-rules.json)
Expand Down
Loading
Loading