add-api-route
Add a new JobPilot API route or module the standard way - contracts schema, controller, service, response schema, optional web hook. Use for "add an endpoint",…
One autonomous Pilot cycle - fetch the agenda, execute the top item, journal, exit. Injected by the terminal host - not for manual invocation loops.
$ npx -y skills add suxrobGM/jobpilot --skill pilot --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/pilotContext preview
The summary Claude sees to decide when to auto-load this skill.
One autonomous Pilot cycle - fetch the agenda, execute the top item, journal, exit. Injected by the terminal host - not for manual invocation loops.
name: pilot description: One autonomous Pilot cycle - fetch the agenda, execute the top item, journal, exit. Injected by the terminal host - not for manual invocation loops. argument-hint: "(none - injected by the terminal host)"
JobPilot's autonomous mode: the host re-injects this skill perpetually, so each invocation is **one stateless cycle** - sense, decide, act, record, exit. All state lives in the API; nothing survives between invocations except what you write there. Do **exactly one** agenda item (at most one worker delegation, one browser activity), journal it, print the sentinel, stop.
Follow `../_shared/setup.md` - health check `GET /api/health` first; abort with its standard message if down. Then generate a cycle id (uuidgen if present, else a portable fallback):
CYCLE_ID=$(uuidgen 2>/dev/null || cat /proc/sys/kernel/random/uuid 2>/dev/null || od -An -tx1 -N16 /dev/urandom | tr -d ' \n' | sed -E 's/^(.{8})(.{4})(.{4})(.{4})(.{12})$/\1-\2-\3-\4-\5/')Load the pilot state - step 2 breaks priority ties with its goals text. No run-state check here: the host gates the loop.
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" "$JOBPILOT_API/api/pilot"
AGENDA=$(curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/agenda/refresh") AGENDA_VERSION=$(echo "$AGENDA" | jq -r '.version')
A `409` means the pilot was stopped mid-cycle - a rare race the host normally gates. Journal and exit empty.
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/journal" \
-H 'content-type: application/json' \
-d "$(jq -n --arg cid "$CYCLE_ID" \
'{cycleId:$cid, entries:[{kind:"cycle", summary:"Pilot is stopped.", detail:{status:"empty", sleepSeconds:3600}}]}')"Print `[[JOBPILOT_CYCLE cycle=$CYCLE_ID status=empty sleep=3600]]` as the final line, stop.
If `.items` is empty:
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/journal" \
-H 'content-type: application/json' \
-d "$(jq -n --arg cid "$CYCLE_ID" --arg s "All caught up - nothing needs doing; checking back at <nextWakeAt>." \
--argjson detail "$(echo "$AGENDA" | jq '{status:"empty", sleepSeconds:.sleepSeconds}')" \
'{cycleId:$cid, entries:[{kind:"cycle", summary:$s, detail:$detail}]}')"Print `[[JOBPILOT_CYCLE cycle=$CYCLE_ID status=empty sleep=<sleepSeconds>]]` as the final line, stop.
Take the top item - the server already ranked the agenda. If several share priority, break ties with the pilot state's instructions goals text (brief judgment call, not a re-ranking pass).
CLAIM=$(curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/claims" \
-H 'content-type: application/json' \
-d "$(jq -n --arg id "<itemId>" --arg version "$AGENDA_VERSION" '{itemId:$id,agendaVersion:$version}')")
CLAIM_ID=$(echo "$CLAIM" | jq -r '.id')On `409`, re-fetch the agenda once; if still nothing claimable, treat this as an empty cycle (step 1's journal + sentinel). A `409` opening `Already applied` is the duplicate guard - the job is already recorded `skipped`, so claim the next item instead of writing a result yourself. `CLAIM_ID` feeds step 6's release and the **heartbeat** that long branches send to keep the claim alive:
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/claims/$CLAIM_ID/heartbeat"
Read `kinds/<item.kind>.md` and follow it - one file per agenda kind, holding that kind's payload, procedure and journal line. Read **only** the one you claimed, plus any peer file it points you at; the rest are not your cycle's work.
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/journal" \
-H 'content-type: application/json' \
-d "$(jq -n --arg cid "$CYCLE_ID" --arg st "<subjectType>" --arg sid "<subjectId>" --arg a "<narrative>" --arg c "<cycle summary>" \
--argjson cdetail "$(echo "$AGENDA" | jq '{status:"ok", sleepSeconds:.sleepSeconds}')" \
'{cycleId:$cid, entries:[{kind:"action", subjectType:$st, subjectId:$sid, summary:$a}, {kind:"cycle", summary:$c, detail:$cdetail}]}')"Write one `action` entry, human and specific ("Applied to Staff TypeScript Engineer at Acme - score 87.", "Discovered 14 jobs for 'senior typescript remote', 9 scored ≥70.", "Parked Stripe application - needs your salary answer."), and one `cycle` entry summarizing the whole cycle. Both carry `cycleId`; the action entry also carries `subjectType`/`subjectId`. The `cycle` entry's `detail:{status, sleepSeconds}` is the authoritative completion signal the host reads back, so this write and step 7's sentinel are both mandatory - the sentinel is only the fast path.
An action entry may also carry a `detail` object - required for the load-bearing markers on `campaign.strategyReview` / `job.rescanSkipped` / `job.retryFailed`. It is an extra field, never a replacement for the batch:
jq -n --arg cid "$CYCLE_ID" --arg sid "$CID" --arg a "$NARRATIVE" --arg c "<cycle summary>" \
--argjson detail '{"type":"strategyReview"}' --argjson cdetail "$(echo "$AGENDA" | jq '{status:"ok", sleepSeconds:.sleepSeconds}')" \
'{cycleId:$cid, entries:[{kind:"action", subjectType:"campaign", subjectId:$sid, summary:$a, detail:$detail}, {kind:"cycle", summary:$c, detail:$cdetail}]}'If the worker returned `observations`, append each to the **same** journal POST as an extra entry `{kind:"observation", summary:<text>, subjectType:"board", subjectId:<board domain>}` - durable board/site facts only, not per-job trivia.
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" -X POST "$JOBPILOT_API/api/pilot/claims/$CLAIM_ID/release" \
-H 'content-type: application/json' -d '{"outcome":"done"}'An AI agent that applies to jobs for you, on the Claude or Codex subscription you already have.
Repo: suxrobGM/jobpilot
Add a new JobPilot API route or module the standard way - contracts schema, controller, service, response schema, optional web hook. Use for "add an endpoint",…
Review and clean up the given file(s)/folder(s)/module(s): rate organization, find dead code, duplication, coupling, over-engineering, deep nesting, structural…
Create and apply a Prisma migration safely against the remote (tunneled) PostgreSQL. Use for schema changes - "create a migration", "apply migrations", "add a…
Add knip to a TypeScript project and use it to remove dead exports, collapse pass-through barrels, and narrow every export to what another file actually…
Bump the unified JobPilot version (host + plugin), update the changelog, commit, and tag a new release
Rebuild the .NET terminal host, restart it, and wait for /healthz. Use after any C# change under apps/terminal, or when asked to "restart the terminal host".