From 3d99f0442754ae837d32b3766cbd02e19beeb4f0 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 19:26:16 -0700 Subject: [PATCH 1/4] feat(agent-queue): profiles (persona + presets) and single-host deps/DAG (P1-S2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements roadmap §6 (profiles) and §5 deps on the bash runner, backward-compatible (jobs without profile/deps behave exactly as before). Profiles (§6): - profile_get / profile_persona / fm_eff helpers + PROFILES_DIR (AGENT_QUEUE_PROFILES override). A job's `profile:` inherits verify (<- default-verify), capabilities, engine-class, prefers-engine, allowed-scope, review-policy when the job omits them; job fields always override (precedence job > profile > default). Resolution runs via fm_eff inside the capability gate and resolve_engine, so inherited caps/engine-class take effect before launch. - persona injection: the profile's persona block is prepended to the stripped body fed to the engine (job .md unchanged on disk; nothing secret logged). - allowed-scope guardrail (WARN-ONLY): scope_check logs a non-blocking WARNING + records scope_warning= for changed paths outside the globs; path_in_scope is a pure, unit-testable matcher (`dir/**` = subtree). deps / DAG, single host (§5): - deps reference other jobs by idempotency-key. dep_satisfied: shipped/ (hard) or shipped/+testing/ (deps-mode: soft). deps_unmet drives a block-with-reason skip in inbox selection (never launched/failed); cmd_status surfaces "blocked (waiting on )". deps_would_cycle rejects cyclic submits on `add`. - _drain_pending: `--once` drains past dep-blocked jobs (idle can't satisfy them) while still waiting on retry/recovery backoff timers. Meta now records effective (inherited) capabilities/engine-class/prefers-engine/ review-policy/allowed-scope so `status` reflects resolved config. --- agent-queue/agent-queue.sh | 249 +++++++++++++++++++++++++++++++++++-- 1 file changed, 237 insertions(+), 12 deletions(-) diff --git a/agent-queue/agent-queue.sh b/agent-queue/agent-queue.sh index 97eae1c..f76b508 100755 --- a/agent-queue/agent-queue.sh +++ b/agent-queue/agent-queue.sh @@ -29,6 +29,8 @@ set -uo pipefail # ── Resolve paths ─────────────────────────────────────────────────── SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" QUEUE_ROOT="${AGENT_QUEUE_ROOT:-$SCRIPT_DIR/queue}" +# Profile catalog dir (persona + capability presets). Override for tests. +PROFILES_DIR="${AGENT_QUEUE_PROFILES:-$SCRIPT_DIR/profiles}" INBOX="$QUEUE_ROOT/inbox" BUILDING="$QUEUE_ROOT/building" REVIEW="$QUEUE_ROOT/review" @@ -120,6 +122,51 @@ lock_key_for() { # _keyhash -> stable filename-safe token for a lock key _keyhash() { printf '%s' "$1" | cksum | awk '{print $1}'; } +# ── Profiles (§6): persona + capability/engine/scope presets ───────── +# +# profile_get [default] -> a single-line value from +# profiles/.md, else the default. +profile_get() { + local pf="$PROFILES_DIR/$1.md" + [[ -f "$pf" ]] || { printf '%s' "${3:-}"; return; } + fm_get "$pf" "$2" "${3:-}" +} + +# profile_persona -> the multi-line `persona: |` block (2-space +# indent stripped), or empty. Used to prepend a persona overlay to the job body. +profile_persona() { + local pf="$PROFILES_DIR/$1.md" + [[ -f "$pf" ]] || return 0 + awk ' + NR==1 && $0!="---" { exit } + NR==1 { infm=1; next } + infm && $0=="---" { exit } + !infm { next } + inpersona { + if ($0 ~ /^[A-Za-z0-9_-]+[ \t]*:/) { inpersona=0 } + else { line=$0; sub(/^ /,"",line); print line; next } + } + $0 ~ /^persona[ \t]*:[ \t]*\|[ \t]*$/ { inpersona=1; next } + ' "$pf" +} + +# fm_eff [default] [profile-key] -> effective value with +# precedence job > profile > built-in default (§6 resolution). The profile is the +# job's `profile:` frontmatter; `profile-key` defaults to `job-key` (e.g. `verify` +# inherits the profile's `default-verify`). Inheritable: verify, capabilities, +# engine-class, prefers-engine, allowed-scope, review-policy. +fm_eff() { + local file=$1 jkey=$2 def=${3:-} pkey=${4:-$2} v prof pv + v=$(fm_get "$file" "$jkey" "") + if [[ -n "$v" ]]; then printf '%s' "$v"; return; fi + prof=$(fm_get "$file" profile "") + if [[ -n "$prof" ]]; then + pv=$(profile_get "$prof" "$pkey" "") + [[ -n "$pv" ]] && { printf '%s' "$pv"; return; } + fi + printf '%s' "$def" +} + # _mtime -> file modification time in epoch seconds (BSD or GNU stat); empty if missing _mtime() { [[ -e "$1" ]] || { echo ""; return; } @@ -356,10 +403,10 @@ resolve_engine() { local f=$1 eng cls prefers eng=$(fm_get "$f" engine "") if [[ -n "$eng" ]]; then printf '%s' "$eng"; return 0; fi - cls=$(fm_get "$f" engine-class "") + cls=$(fm_eff "$f" engine-class "") # inherit engine-class from the job's profile if [[ -z "$cls" ]]; then printf '%s' "$DEFAULT_ENGINE"; return 0; fi local class_engines; class_engines=$(engine_class_engines "$cls") - prefers=$(fm_get "$f" prefers-engine "") + prefers=$(fm_eff "$f" prefers-engine "") local ordered=() seen=" " p c if [[ -n "$prefers" ]]; then while IFS= read -r p; do @@ -377,6 +424,134 @@ resolve_engine() { printf '%s' "" } +# ── deps / DAG, single host (§5) ───────────────────────────────────── +# deps reference other jobs by their (author-controlled) `idempotency-key`. +# +# _key_in_dir -> 0 if some .md in has idempotency-key == key. +_key_in_dir() { + local key=$1 d=$2 ef + for ef in "$d"/*.md; do + [[ -e "$ef" ]] || continue + [[ "$(fm_get "$ef" idempotency-key "")" == "$key" ]] && return 0 + done + return 1 +} + +# dep_satisfied -> 0 when the dep is met: a job with is in +# shipped/ (default), or shipped/ OR testing/ when mode is `soft`. +dep_satisfied() { + local key=$1 mode=$2 + _key_in_dir "$key" "$SHIPPED" && return 0 + [[ "$mode" == soft ]] && _key_in_dir "$key" "$TESTING" && return 0 + return 1 +} + +# deps_unmet -> space-separated list of this job's UNMET dep keys (empty if +# none / no deps). `deps-mode` (hard|soft) is job-level. +deps_unmet() { + local f=$1 keys mode k unmet="" + keys=$(parse_list "$(fm_get "$f" deps "")" | tr '\n' ' ') + [[ -n "${keys// /}" ]] || { printf ''; return 0; } + mode=$(fm_get "$f" deps-mode "hard") + for k in $keys; do + [[ -n "$k" ]] || continue + dep_satisfied "$k" "$mode" || unmet+="$k " + done + printf '%s' "${unmet% }" +} + +# _deps_of_key -> dep keys (space-separated) of the job carrying , +# scanned across inbox + active stages. +_deps_of_key() { + local key=$1 d ef + for d in "$INBOX" "$BUILDING" "$REVIEW" "$TESTING" "$SHIPPED"; do + for ef in "$d"/*.md; do + [[ -e "$ef" ]] || continue + [[ "$(fm_get "$ef" idempotency-key "")" == "$key" ]] || continue + parse_list "$(fm_get "$ef" deps "")" | tr '\n' ' ' + return 0 + done + done +} + +# deps_would_cycle -> 0 if adding a job with +# depending on would create a cycle (BFS over existing key->deps edges +# back to new-key; also catches self-dependency). +deps_would_cycle() { + local newkey=$1 newdeps=$2 visited=" " frontier next k d kd + [[ -n "$newkey" ]] || return 1 + _in_list "$newkey" "$newdeps" && return 0 + frontier="$newdeps" + while [[ -n "${frontier// /}" ]]; do + next="" + for k in $frontier; do + [[ -n "$k" ]] || continue + [[ "$k" == "$newkey" ]] && return 0 + case "$visited" in *" $k "*) continue;; esac + visited+="$k " + kd=$(_deps_of_key "$k") + for d in $kd; do next+="$d "; done + done + frontier="$next" + done + return 1 +} + +# _drain_pending -> 0 if some inbox job can still make progress on its own: it is +# runnable now, or it is waiting on a retry/recovery backoff (which elapses with +# time). A job blocked ONLY by unmet deps is NOT pending while the loop is idle +# (no running job can satisfy its deps), so `--once` can drain past it. +_drain_pending() { + local cand cj ne now; now=$(date +%s) + for cand in "$INBOX"/*.md; do + [[ -e "$cand" ]] || continue + cj=$(basename "$cand"); cj=${cj%.md} + ne=$(grep '^next_eligible=' "$STATE/$cj.meta" 2>/dev/null | tail -1 | cut -d= -f2) + if [[ "$ne" =~ ^[0-9]+$ ]] && [[ "$ne" -gt "$now" ]]; then return 0; fi + [[ -n "$(deps_unmet "$cand")" ]] && continue + return 0 + done + return 1 +} + +# ── allowed-scope guardrail (§6/§12) — WARN-ONLY this phase ─────────── +# path_in_scope -> 0 if matches any allowed-scope glob +# (`dir/**` matches the whole subtree; `*` matches across `/`). Pure + testable. +path_in_scope() { + local path=$1 globs=$2 g pat + for g in $globs; do + [[ -n "$g" ]] || continue + pat=${g//\*\*/\*} + # shellcheck disable=SC2053 + [[ "$path" == $pat ]] && return 0 + [[ "$path" == "$g"/* ]] && return 0 + done + return 1 +} + +# scope_check -> log a WARNING (non-blocking) +# for changed paths outside allowed-scope. Records scope_warning= in the meta. +scope_check() { + local cwd=$1 base=$2 scope=$3 logf=$4 metaf=$5 changed globs p out="" + _is_git_repo "$cwd" || return 0 + if [[ -n "$base" ]]; then + changed=$(git -C "$cwd" diff --name-only "$base" HEAD 2>/dev/null) + else + changed=$(git -C "$cwd" diff --name-only HEAD 2>/dev/null) + fi + [[ -n "$changed" ]] || return 0 + globs=$(parse_list "$scope" | tr '\n' ' ') + [[ -n "${globs// /}" ]] || return 0 + while IFS= read -r p; do + [[ -n "$p" ]] || continue + path_in_scope "$p" "$globs" || out+="$p " + done <<< "$changed" + if [[ -n "$out" ]]; then + echo "WARNING: allowed-scope violation (warn-only) — changed outside [$globs]: ${out% }" >> "$logf" + echo "scope_warning=${out% }" >> "$metaf" + fi +} + # ── Engine driver: builds argv into AGENT_CMD[]; sets AGENT_STDIN if the ── # prompt should be fed on stdin (claude/codex) rather than a flag. $pf is the # frontmatter-STRIPPED body file, so a body starting with '--' is never @@ -424,8 +599,9 @@ run_worker() { # The worker only ever APPENDS (ended/exit/result) to avoid a truncation race. # ── Capability gate (§5/§8 single-host): if the job declares `capabilities` - # this host does not satisfy, route to failed/ WITHOUT launching the agent. ── - local req_caps; req_caps=$(parse_list "$(fm_get "$doing_file" capabilities "")" | tr '\n' ' ') + # (own or inherited from its profile) this host does not satisfy, route to + # failed/ WITHOUT launching the agent. ── + local req_caps; req_caps=$(parse_list "$(fm_eff "$doing_file" capabilities "")" | tr '\n' ' ') if [[ -n "${req_caps// /}" ]]; then local avail; avail=$(detect_capabilities) if ! caps_match "$req_caps" "$avail"; then @@ -474,6 +650,16 @@ run_worker() { # Strip our frontmatter so the agent only sees the task body. local bodyf="$STATE/$job.body.md" strip_frontmatter "$doing_file" > "$bodyf" + # ── Persona injection (§6): prepend the profile's persona to the body fed to + # the engine (job body unchanged on disk). Secrets are never logged. ── + local prof; prof=$(fm_get "$doing_file" profile "") + if [[ -n "$prof" ]]; then + local persona; persona=$(profile_persona "$prof") + if [[ -n "$persona" ]]; then + { printf '%s\n\n' "$persona"; cat "$bodyf"; } > "$bodyf.tmp" && mv "$bodyf.tmp" "$bodyf" + echo "profile: injected persona overlay from '$prof'" >> "$logf" + fi + fi build_agent_cmd "$engine" "$bodyf" "$yolo" # ── WIP checkpoint setup (§25.2): on a git cwd, create/checkout aq/wip/ @@ -547,6 +733,11 @@ run_worker() { _numstat_into_meta "$cwd" "$WIP_BASE" "$metaf" parse_usage "$engine" "$logf" >> "$metaf" + # ── allowed-scope guardrail (§6) — WARN-ONLY: flag out-of-scope changes but + # never block the job this phase. Scope may be inherited from the profile. ── + local scope; scope=$(fm_eff "$doing_file" allowed-scope "" allowed-scope) + [[ -n "$scope" ]] && scope_check "$cwd" "$WIP_BASE" "$scope" "$logf" "$metaf" + if $timed_out; then echo "TIMED OUT after ${tmo}s (rc=$rc): $(date)" >> "$logf" _finish_failure "$job" "$doing_file" "$metaf" "$logf" "timeout" "$rc" "$started" @@ -558,7 +749,8 @@ run_worker() { local review_file="$REVIEW/$job.md" echo "exit=$rc" >> "$metaf" echo "completed OK (rc=0): landed in review — $(date)" >> "$logf" - local verify; verify=$(fm_get "$review_file" verify "$DEFAULT_VERIFY") + # verify is job-level, else inherited from the profile's default-verify. + local verify; verify=$(fm_eff "$review_file" verify "$DEFAULT_VERIFY" default-verify) if [[ -z "$verify" ]]; then _meta_end "$metaf" "review" "$started" echo "no verify command — parked in review for manual promote: $(date)" >> "$logf" @@ -896,6 +1088,13 @@ cmd_add() { done fi + # ── dep cycle detection (§5): reject a submit that would create a cycle in the + # idempotency-key dependency graph (inbox + active stages). ── + local newdeps; newdeps=$(parse_list "$(fm_get "$file" deps "")" | tr '\n' ' ') + if [[ -n "${newdeps// /}" ]] && deps_would_cycle "$idem" "$newdeps"; then + die "dependency cycle detected: job (key '${idem:-}') with deps [${newdeps% }] would create a cycle — refusing." + fi + local base; base=$(basename "$file") local stamp; stamp=$(date +%Y%m%d-%H%M%S) local dest="$INBOX/${stamp}__${base}" @@ -963,6 +1162,8 @@ cmd_run() { cand_job=$(basename "$cand"); cand_job=${cand_job%.md} cand_ne=$(grep '^next_eligible=' "$STATE/$cand_job.meta" 2>/dev/null | tail -1 | cut -d= -f2) if [[ "$cand_ne" =~ ^[0-9]+$ ]] && [[ "$cand_ne" -gt "$now_s" ]]; then continue; fi + # skip jobs whose deps (§5 DAG) are unmet — blocked, re-evaluated next loop + if [[ -n "$(deps_unmet "$cand")" ]]; then continue; fi next="$cand"; break done < <(inbox_sorted) [[ -z "$next" ]] && break @@ -997,16 +1198,17 @@ cmd_run() { echo "attempts=$w_attempts" echo "priority=$(fm_get "$doing_file" priority medium)" echo "profile=$(fm_get "$doing_file" profile "")" - echo "engine_class=$(fm_get "$doing_file" engine-class "")" - echo "capabilities=$(fm_get "$doing_file" capabilities "")" + echo "engine_class=$(fm_eff "$doing_file" engine-class "")" + echo "capabilities=$(fm_eff "$doing_file" capabilities "")" echo "prefers=$(fm_get "$doing_file" prefers "")" - echo "prefers_engine=$(fm_get "$doing_file" prefers-engine "")" + echo "prefers_engine=$(fm_eff "$doing_file" prefers-engine "")" + echo "allowed_scope=$(fm_eff "$doing_file" allowed-scope "" allowed-scope)" echo "budget=$(fm_get "$doing_file" budget "")" echo "deps=$(fm_get "$doing_file" deps "")" echo "deps_mode=$(fm_get "$doing_file" deps-mode "")" echo "idempotency_key=$(fm_get "$doing_file" idempotency-key "")" echo "retry=$(fm_get "$doing_file" retry "")" - echo "review_policy=$(fm_get "$doing_file" review-policy "")" + echo "review_policy=$(fm_eff "$doing_file" review-policy "" review-policy)" echo "artifacts=$(fm_get "$doing_file" artifacts "")" echo "tracker_item=$(fm_get "$doing_file" tracker-item "")" } > "$STATE/$job.meta" @@ -1018,8 +1220,11 @@ cmd_run() { done if $once; then - [[ "$(active_workers)" -eq 0 && -z "$(ls -1 "$INBOX"/*.md 2>/dev/null)" ]] && { - log "drain complete — inbox empty, no workers running"; rm -f "$STATE/daemon.pid"; exit 0; } + # drain when no worker is running and nothing in inbox can still progress on + # its own (backoff jobs still count as pending; dep-blocked jobs do not). + if [[ "$(active_workers)" -eq 0 ]] && ! _drain_pending; then + log "drain complete — no runnable work, no workers running"; rm -f "$STATE/daemon.pid"; exit 0 + fi fi sleep "$POLL_SECONDS" done @@ -1072,6 +1277,18 @@ cmd_status() { printf ' %s%s%s\n' "$C_DIM" "$(_insights_line "$f")" "$C_RESET" done $printed || printf ' %sno workers running%s\n' "$C_DIM" "$C_RESET" + + # blocked jobs (unmet deps, §5) — waiting in inbox/, re-evaluated each loop + local bf bj unmet bprinted=false + for bf in "$INBOX"/*.md; do + [[ -e "$bf" ]] || continue + unmet=$(deps_unmet "$bf") + [[ -n "$unmet" ]] || continue + if ! $bprinted; then echo; printf ' %sBLOCKED%s\n' "$C_BOLD" "$C_RESET"; bprinted=true; fi + bj=$(basename "$bf"); bj=${bj%.md} + printf ' %s%-26s%s %sblocked (waiting on: %s)%s\n' \ + "$C_BOLD" "$bj" "$C_RESET" "$C_YEL" "$unmet" "$C_RESET" + done echo } @@ -1328,10 +1545,18 @@ ${C_BOLD}TASK FRONTMATTER${C_RESET} (top of each .md) capabilities: [os:any, node>=20, has:git] # hard host requirements; unmet -> failed (capability_mismatch) idempotency-key: my-task-1 # re-adding same key+body = no-op; same key+different body = reject/supersede retry: { max: 2, backoff: 5m, on: [timeout, verify_failed, crash] } # requeue on these classes up to max, then retries_exhausted + profile: backend-engineer # inherit persona + verify/caps/engine-class/scope/review-policy (job fields override) + deps: [other-key] # block until each idempotency-key is shipped/ (or testing/ if deps-mode: soft) + deps-mode: soft # soft = a dep also counts as satisfied while in testing/ # --- reserved (parsed + shown in status, but no-op until a later phase) --- - profile: prefers: budget: deps: deps-mode: review-policy: artifacts: tracker-item: + prefers: budget: review-policy: artifacts: tracker-item: --- +${C_BOLD}PROFILES${C_RESET} profiles/.md presets persona + capabilities + default-verify + engine-class + + prefers-engine + allowed-scope + review-policy. A job's own fields always override. + Catalog: developer, backend-engineer, frontend-engineer, ux-designer, ui-designer, + qa, reviewer, docs-writer, planner(reserved). + ${C_BOLD}RESILIENCE${C_RESET} crash-safe: orphaned building/ jobs (dead worker) are recovered to inbox/ on 'run' startup; git-repo cwd work is checkpointed to branch aq/wip/ on every exit (resumed on retry); 'retry' requeues failures with backoff. See 'insights'. From f2dabdeb814d55e2eaed11c42cabb0a0a0899237 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 19:26:26 -0700 Subject: [PATCH 2/4] feat(agent-queue): starter profile catalog (P1-S2) profiles/.md presets (name, persona, capabilities, default-verify, engine-class, prefers-engine, allowed-scope, review-policy) for developer, backend-engineer, frontend-engineer, ux-designer, ui-designer, qa, reviewer, docs-writer, and a reserved planner. --- agent-queue/profiles/backend-engineer.md | 19 +++++++++++++++++++ agent-queue/profiles/developer.md | 20 ++++++++++++++++++++ agent-queue/profiles/docs-writer.md | 18 ++++++++++++++++++ agent-queue/profiles/frontend-engineer.md | 18 ++++++++++++++++++ agent-queue/profiles/planner.md | 19 +++++++++++++++++++ agent-queue/profiles/qa.md | 18 ++++++++++++++++++ agent-queue/profiles/reviewer.md | 19 +++++++++++++++++++ agent-queue/profiles/ui-designer.md | 19 +++++++++++++++++++ agent-queue/profiles/ux-designer.md | 19 +++++++++++++++++++ 9 files changed, 169 insertions(+) create mode 100644 agent-queue/profiles/backend-engineer.md create mode 100644 agent-queue/profiles/developer.md create mode 100644 agent-queue/profiles/docs-writer.md create mode 100644 agent-queue/profiles/frontend-engineer.md create mode 100644 agent-queue/profiles/planner.md create mode 100644 agent-queue/profiles/qa.md create mode 100644 agent-queue/profiles/reviewer.md create mode 100644 agent-queue/profiles/ui-designer.md create mode 100644 agent-queue/profiles/ux-designer.md diff --git a/agent-queue/profiles/backend-engineer.md b/agent-queue/profiles/backend-engineer.md new file mode 100644 index 0000000..dd62a94 --- /dev/null +++ b/agent-queue/profiles/backend-engineer.md @@ -0,0 +1,19 @@ +--- +name: backend-engineer +persona: | + You are a senior backend engineer. Favor minimal, well-tested changes. Respect + service boundaries, validate inputs, handle errors explicitly, and never log + secrets. Prefer existing libraries and patterns over new dependencies. Keep + migrations and API changes backward-compatible unless the task says otherwise. +capabilities: [os:any, node>=20, has:pnpm] +default-verify: pnpm -s typecheck && pnpm -s test +engine-class: agentic-coder +prefers-engine: [devin, claude] +allowed-scope: ["backend/**", "services/**", "packages/**"] +review-policy: manual +--- + +# backend-engineer + +Server-side work. Inherits a typecheck+test verify gate and a scope limited to +backend/service/package code. diff --git a/agent-queue/profiles/developer.md b/agent-queue/profiles/developer.md new file mode 100644 index 0000000..9a3d0fd --- /dev/null +++ b/agent-queue/profiles/developer.md @@ -0,0 +1,20 @@ +--- +name: developer +persona: | + You are a pragmatic senior software engineer. Make the smallest correct change + that satisfies the task. Match the surrounding code style and existing patterns, + keep diffs focused, and never commit secrets. Add or update tests when you change + behavior, and explain non-obvious decisions briefly in the commit message. +capabilities: [os:any, has:git] +default-verify: +engine-class: agentic-coder +prefers-engine: [devin, claude, codex] +allowed-scope: ["**"] +review-policy: manual +--- + +# developer + +General-purpose engineering profile. No default verify (parks in review for a +human gate) and an unrestricted scope — pick a more specific profile when you +want a tighter blast radius or an automatic QA gate. diff --git a/agent-queue/profiles/docs-writer.md b/agent-queue/profiles/docs-writer.md new file mode 100644 index 0000000..e6620c9 --- /dev/null +++ b/agent-queue/profiles/docs-writer.md @@ -0,0 +1,18 @@ +--- +name: docs-writer +persona: | + You are a technical writer. Produce clear, accurate documentation that matches + the repository's existing voice and structure. Update READMEs, guides, and + references; keep examples runnable and links valid. Do not change source code + beyond doc comments. Never include secrets in examples. +capabilities: [os:any] +default-verify: +engine-class: agentic-coder +prefers-engine: [claude, devin] +allowed-scope: ["docs/**", "**/*.md", "**/*.mdx"] +review-policy: manual +--- + +# docs-writer + +Documentation profile. Scoped to docs + markdown; parks in review for a human read. diff --git a/agent-queue/profiles/frontend-engineer.md b/agent-queue/profiles/frontend-engineer.md new file mode 100644 index 0000000..ff45645 --- /dev/null +++ b/agent-queue/profiles/frontend-engineer.md @@ -0,0 +1,18 @@ +--- +name: frontend-engineer +persona: | + You are a senior frontend engineer. Build accessible, responsive UI that matches + the existing component library and design tokens. Keep state management simple, + avoid unnecessary dependencies, and ensure type-safety. Verify the build and + tests pass before finishing; never hardcode secrets or API keys. +capabilities: [os:any, node>=20, has:pnpm] +default-verify: pnpm -s typecheck && pnpm -s build +engine-class: agentic-coder +prefers-engine: [claude, devin] +allowed-scope: ["dashboards/**", "apps/**", "packages/ui/**", "src/**"] +review-policy: manual +--- + +# frontend-engineer + +Client/UI work. Inherits a typecheck+build gate and a UI-oriented scope. diff --git a/agent-queue/profiles/planner.md b/agent-queue/profiles/planner.md new file mode 100644 index 0000000..84be406 --- /dev/null +++ b/agent-queue/profiles/planner.md @@ -0,0 +1,19 @@ +--- +name: planner +persona: | + You are a planning agent. Break an objective into a dependency-ordered set of + small, well-scoped tasks, each mappable to a job .md (with a profile, scope, and + verify). Output the plan as markdown; do not implement the tasks yourself. +capabilities: [os:any] +default-verify: +engine-class: agentic-coder +prefers-engine: [claude] +allowed-scope: ["docs/**", "**/*.md"] +review-policy: manual +--- + +# planner (reserved) + +Reserved for a future planning/decomposition flow that emits child jobs with +`deps:` wiring. Usable today as a docs-scoped persona; automatic job emission is +a later slice. diff --git a/agent-queue/profiles/qa.md b/agent-queue/profiles/qa.md new file mode 100644 index 0000000..8f32aac --- /dev/null +++ b/agent-queue/profiles/qa.md @@ -0,0 +1,18 @@ +--- +name: qa +persona: | + You are a QA engineer. Write and strengthen tests; reproduce bugs with a failing + test first, then confirm the fix. Cover edge cases, error paths, and regressions. + Do not weaken or delete existing tests to make a suite pass — fix the cause. + Keep tests deterministic and fast. +capabilities: [os:any, node>=20, has:pnpm] +default-verify: pnpm -s test +engine-class: agentic-coder +prefers-engine: [codex, claude] +allowed-scope: ["**/*.test.*", "**/*.spec.*", "test/**", "tests/**", "e2e/**"] +review-policy: manual +--- + +# qa + +Test-focused profile. Inherits a `pnpm -s test` gate and a test-files scope. diff --git a/agent-queue/profiles/reviewer.md b/agent-queue/profiles/reviewer.md new file mode 100644 index 0000000..cf87151 --- /dev/null +++ b/agent-queue/profiles/reviewer.md @@ -0,0 +1,19 @@ +--- +name: reviewer +persona: | + You are a code reviewer. Do NOT modify code. Read the diff/changes and produce a + concise review: correctness, security, tests, readability, and scope adherence. + Flag risky or out-of-scope changes and supply-chain concerns (edits to shared + packages). Output findings as markdown with severity labels. +capabilities: [os:any, has:git] +default-verify: +engine-class: review-only +prefers-engine: [claude] +allowed-scope: ["docs/**", "**/*.md"] +review-policy: manual +--- + +# reviewer + +Read-only review profile. `engine-class: review-only` has no concrete runner +mapping yet (reserved) — use an explicit `engine:` until a review engine lands. diff --git a/agent-queue/profiles/ui-designer.md b/agent-queue/profiles/ui-designer.md new file mode 100644 index 0000000..94cdc3b --- /dev/null +++ b/agent-queue/profiles/ui-designer.md @@ -0,0 +1,19 @@ +--- +name: ui-designer +persona: | + You are a UI/visual designer. Focus on visual hierarchy, spacing, color, and + typography using the existing design tokens and component library. Keep changes + consistent with the design system, ensure sufficient contrast, and respect + light/dark themes. Prefer token references over hardcoded values. +capabilities: [os:any, node>=20] +default-verify: +engine-class: agentic-coder +prefers-engine: [claude, devin] +allowed-scope: ["packages/ui/**", "packages/design-tokens/**", "**/*.css", "design/**"] +review-policy: manual +--- + +# ui-designer + +Visual/design-system work scoped to UI + tokens + styles. Parks in review for a +human visual check. diff --git a/agent-queue/profiles/ux-designer.md b/agent-queue/profiles/ux-designer.md new file mode 100644 index 0000000..744d193 --- /dev/null +++ b/agent-queue/profiles/ux-designer.md @@ -0,0 +1,19 @@ +--- +name: ux-designer +persona: | + You are a UX designer. Focus on user flows, information architecture, and + interaction states (empty, loading, error, success). Produce wireframes, + flow descriptions, and copy as markdown/specs. Justify decisions with usability + heuristics and accessibility (WCAG) considerations. Do not change production code. +capabilities: [os:any] +default-verify: +engine-class: agentic-coder +prefers-engine: [claude] +allowed-scope: ["docs/**", "design/**", "**/*.md"] +review-policy: manual +--- + +# ux-designer + +Flows, IA, and interaction specs. Documentation-scoped; parks in review for human +sign-off (no automatic verify gate). From 71d8a7cd4ee627769cca16acd9b6e175ee10e708 Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 19:26:26 -0700 Subject: [PATCH 3/4] test(agent-queue): profiles + deps/DAG selftest cases (P1-S2) Adds (never weakens) temp-catalog + temp-git cases: profile verify inheritance + job-override precedence, persona-injection golden, profile capability inheritance, allowed-scope warn-only + path_in_scope unit, deps block->run, deps-mode soft (testing/), and submit-time cycle rejection. Full suite green (46 checks). --- agent-queue/selftest.sh | 165 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 165 insertions(+) diff --git a/agent-queue/selftest.sh b/agent-queue/selftest.sh index 1f150e1..ffeb0be 100755 --- a/agent-queue/selftest.sh +++ b/agent-queue/selftest.sh @@ -420,4 +420,169 @@ else printf '%s\n' "$out" >&2; fail "insights aggregate rollup missing/incorrect" fi +# ───────────────────────────────────────────────────────────────────── +# Phase 1 — Slice 2 cases (profiles + deps/DAG, single host). +# Uses a temp profile catalog (AGENT_QUEUE_PROFILES) + temp git repos. +# ───────────────────────────────────────────────────────────────────── +profdir="$tmp/profiles"; mkdir -p "$profdir" +printf '%s\n' '---' 'name: vfail' 'persona: |' ' PERSONA-VFAIL' 'default-verify: false' '---' > "$profdir/vfail.md" +printf '%s\n' '---' 'name: vpass' 'default-verify: true' '---' > "$profdir/vpass.md" +printf '%s\n' '---' 'name: capreq' 'capabilities: [has:definitely-not-installed]' '---' > "$profdir/capreq.md" +printf '%s\n' '---' 'name: personap' 'persona: |' ' PERSONA-MARKER-XYZ' ' second persona line' 'default-verify: true' '---' > "$profdir/personap.md" +printf '%s\n' '---' 'name: scoped' 'allowed-scope: [backend/**]' '---' > "$profdir/scoped.md" +export AGENT_QUEUE_PROFILES="$profdir" +funcs="$tmp/aq-funcs.sh"; sed '/^main "\$@"/d' "$AQ" > "$funcs" + +# 19. profile inherits default-verify: vfail (verify=false) → failed/verify_failed; +# vpass (verify=true) → testing/. +export AGENT_QUEUE_ROOT="$tmp/queue-pverify" +"$AQ" init >/dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'profile: vfail' '---' '' '# pv-fail' \ + > "$AGENT_QUEUE_ROOT/inbox/pvfail.md" +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/failed/pvfail.md >/dev/null 2>&1 \ + && [ "$(metaval "$AGENT_QUEUE_ROOT/.state/pvfail.meta" result)" = "verify_failed" ]; then + pass "profile inherit: default-verify=false → failed/ (verify_failed)" +else + fail "profile verify=false did not route to failed (result=$(metaval "$AGENT_QUEUE_ROOT/.state/pvfail.meta" result))" +fi +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'profile: vpass' '---' '' '# pv-pass' \ + > "$AGENT_QUEUE_ROOT/inbox/pvpass.md" +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/testing/pvpass.md >/dev/null 2>&1; then + pass "profile inherit: default-verify=true → testing/" +else + fail "profile verify=true did not reach testing/" +fi + +# 19b. job-level verify overrides the profile (precedence job > profile). +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'profile: vfail' 'verify: true' '---' '' '# override' \ + > "$AGENT_QUEUE_ROOT/inbox/pvoverride.md" +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +ls "$AGENT_QUEUE_ROOT"/testing/pvoverride.md >/dev/null 2>&1 \ + && pass "profile precedence: job verify overrides profile default-verify" \ + || fail "job-level verify did not override profile" + +# 20. persona injection (golden): the body fed to the engine begins with the +# profile persona. A stub copies its --prompt-file to a sentinel. +export AGENT_QUEUE_ROOT="$tmp/queue-persona" +sentinel="$tmp/persona-body.txt"; rm -f "$sentinel" +copystub="$tmp/copy-engine" +cat > "$copystub" </dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'profile: personap' '---' '' 'TASK-BODY-LINE' \ + > "$AGENT_QUEUE_ROOT/inbox/personajob.md" +DEVIN_BIN="$copystub" "$AQ" run --once >/dev/null 2>&1 +if [ "$(head -1 "$sentinel" 2>/dev/null)" = "PERSONA-MARKER-XYZ" ] \ + && grep -q 'TASK-BODY-LINE' "$sentinel" 2>/dev/null; then + pass "persona injection: engine body begins with profile persona, task preserved" +else + echo "body head: $(head -3 "$sentinel" 2>/dev/null)" >&2 + fail "persona was not prepended to the engine body" +fi + +# 21. profile capability inheritance: a job omitting capabilities inherits the +# profile's → unmet → failed/ capability_mismatch, agent never launched. +export AGENT_QUEUE_ROOT="$tmp/queue-pcaps" +launchflag="$tmp/pcaps-launched"; rm -f "$launchflag" +launchstub3="$tmp/cap-launch3" +printf '#!/usr/bin/env bash\ntouch %q\nexit 0\n' "$launchflag" > "$launchstub3"; chmod +x "$launchstub3" +"$AQ" init >/dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'profile: capreq' '---' '' '# pcaps' \ + > "$AGENT_QUEUE_ROOT/inbox/pcapsjob.md" +DEVIN_BIN="$launchstub3" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/failed/pcapsjob.md >/dev/null 2>&1 \ + && [ "$(metaval "$AGENT_QUEUE_ROOT/.state/pcapsjob.meta" result)" = "capability_mismatch" ] \ + && [ ! -e "$launchflag" ]; then + pass "profile caps inheritance: unmet inherited capability → capability_mismatch (no launch)" +else + fail "profile caps inheritance failed (result=$(metaval "$AGENT_QUEUE_ROOT/.state/pcapsjob.meta" result) launched=$([ -e "$launchflag" ] && echo yes || echo no))" +fi + +# 22. allowed-scope warn-only: an out-of-scope change logs a WARNING and the job +# still succeeds; plus a direct path_in_scope unit check. +export AGENT_QUEUE_ROOT="$tmp/queue-scope" +repos="$tmp/repo-scope"; mkrepo "$repos" +scopestub="$tmp/scope-engine" +printf '#!/usr/bin/env bash\nmkdir -p frontend && echo changed > frontend/out.txt\nexit 0\n' > "$scopestub" +chmod +x "$scopestub" +"$AQ" init >/dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $repos" 'yolo: true' 'profile: scoped' '---' '' '# scope task' \ + > "$AGENT_QUEUE_ROOT/inbox/scopejob.md" +DEVIN_BIN="$scopestub" "$AQ" run --once >/dev/null 2>&1 +if grep -q 'allowed-scope violation' "$AGENT_QUEUE_ROOT/logs/scopejob.log" 2>/dev/null \ + && ls "$AGENT_QUEUE_ROOT"/review/scopejob.md >/dev/null 2>&1; then + pass "allowed-scope: out-of-scope change WARNS (warn-only) and job still succeeds" +else + fail "allowed-scope warn-only did not warn / job did not succeed" +fi +if bash -c 'set -uo pipefail; source "'"$funcs"'"; path_in_scope "backend/a/b.ts" "backend/**" && ! path_in_scope "frontend/x.ts" "backend/**"'; then + pass "allowed-scope: path_in_scope matches subtree, rejects outside (unit)" +else + fail "path_in_scope unit logic wrong" +fi + +# 23. deps block→run: B deps:[keyA] stays blocked until A is shipped/, then runs. +export AGENT_QUEUE_ROOT="$tmp/queue-deps" +"$AQ" init >/dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyA' '---' '' '# A' \ + > "$AGENT_QUEUE_ROOT/inbox/jobA.md" +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyB' 'deps: [keyA]' '---' '' '# B' \ + > "$AGENT_QUEUE_ROOT/inbox/jobB.md" +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/inbox/jobB.md >/dev/null 2>&1 && ls "$AGENT_QUEUE_ROOT"/review/jobA.md >/dev/null 2>&1; then + pass "deps: B stays blocked in inbox while A is unshipped" +else + fail "deps: B should be blocked while A unshipped (A=$(ls "$AGENT_QUEUE_ROOT"/review 2>/dev/null) B-in-inbox=$(ls "$AGENT_QUEUE_ROOT"/inbox 2>/dev/null))" +fi +# status surfaces the blocked job +"$AQ" status 2>/dev/null | grep -q 'blocked (waiting on: keyA)' \ + && pass "deps: status surfaces 'blocked (waiting on: keyA)'" \ + || fail "deps: status did not surface blocked job" +# ship A (review -> testing -> shipped), then B becomes runnable +"$AQ" promote jobA >/dev/null 2>&1 # review -> testing +"$AQ" promote jobA >/dev/null 2>&1 # testing -> shipped +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/review/jobB.md >/dev/null 2>&1; then + pass "deps: once A is shipped, B unblocks and completes" +else + fail "deps: B did not run after A shipped" +fi + +# 24. deps-mode soft: dep satisfied when the dependency is in testing/. +export AGENT_QUEUE_ROOT="$tmp/queue-depsoft" +"$AQ" init >/dev/null +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyA' 'verify: true' '---' '' '# A-soft' \ + > "$AGENT_QUEUE_ROOT/inbox/sjobA.md" +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyC' 'deps: [keyA]' 'deps-mode: soft' '---' '' '# C-soft' \ + > "$AGENT_QUEUE_ROOT/inbox/sjobC.md" +DEVIN_BIN="$stub" "$AQ" run --once >/dev/null 2>&1 +if ls "$AGENT_QUEUE_ROOT"/testing/sjobA.md >/dev/null 2>&1 && ls "$AGENT_QUEUE_ROOT"/review/sjobC.md >/dev/null 2>&1; then + pass "deps-mode soft: dep satisfied while dependency is in testing/" +else + fail "deps-mode soft did not unblock from testing/ (A=$(ls "$AGENT_QUEUE_ROOT"/testing 2>/dev/null) C=$(ls "$AGENT_QUEUE_ROOT"/review 2>/dev/null))" +fi + +# 25. cycle detection: adding A deps:[keyB] while B deps:[keyA] exists is rejected. +export AGENT_QUEUE_ROOT="$tmp/queue-cycle" +"$AQ" init >/dev/null +cycB="$tmp/cyc-b.md" +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyB' 'deps: [keyA]' '---' '' '# cyc B' > "$cycB" +"$AQ" add "$cycB" >/dev/null 2>&1 # B added (blocked on keyA — allowed) +cycA="$tmp/cyc-a.md" +printf '%s\n' '---' 'engine: devin' "cwd: $work" 'yolo: true' 'idempotency-key: keyA' 'deps: [keyB]' '---' '' '# cyc A' > "$cycA" +if DEVIN_BIN="$stub" "$AQ" add "$cycA" >/dev/null 2>&1; then + fail "cycle detection: adding A deps:[keyB] while B deps:[keyA] should be rejected" +else + pass "cycle detection: dependency cycle on add is rejected" +fi +unset AGENT_QUEUE_PROFILES + echo "self-test PASS" From e183919c60b7d7f7e6dab364e06354006e98640b Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Fri, 29 May 2026 19:26:33 -0700 Subject: [PATCH 4/4] =?UTF-8?q?docs(agent-queue):=20profiles=20+=20deps=20?= =?UTF-8?q?docs;=20tick=20=C2=A75/=C2=A76=20+=20bump=20Phase=201=20to=2080?= =?UTF-8?q?%=20(P1-S2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README: Profiles & deps section (resolution precedence, persona, allowed-scope warn-only, deps/blocked + cycle detection); manifest table moves profile/deps/deps-mode to active. Roadmap: tick §6 catalog/persona/inheritance/allowed-scope and §5 deps + the §14 profile/deps/scope boxes; add P1-S2 slice note; §0 Phase 1 -> 80%. --- agent-queue/README.md | 40 +++++++++++++++++++++++-- agent-queue/docs/GIGAFACTORY_ROADMAP.md | 26 ++++++++-------- 2 files changed, 52 insertions(+), 14 deletions(-) diff --git a/agent-queue/README.md b/agent-queue/README.md index 46c333e..516cc76 100644 --- a/agent-queue/README.md +++ b/agent-queue/README.md @@ -109,10 +109,10 @@ are otherwise **no-ops until a later phase** (they do not yet affect execution). | `prefers-engine` | **active** | _(none)_ | optional order hint for `engine-class` resolution, e.g. `[claude, devin]` | | `capabilities` | **active** | _(none)_ | hard host requirements, e.g. `[os:any, node>=20, has:git]`. If the host can't satisfy them the job is sent to `failed/` with `result=capability_mismatch` **and the agent is never launched** (grammar below) | | `idempotency-key` | **active** | _(none)_ | dedupe on `add` (semantics below) | -| `profile` | RESERVED | _(none)_ | role/persona + caps (profiles land in a later slice) | +| `profile` | **active** | _(none)_ | inherit persona + verify/caps/engine-class/prefers-engine/allowed-scope/review-policy from `profiles/.md` (job fields override — see **Profiles**) | | `prefers` | RESERVED | _(none)_ | soft routing/affinity hints (e.g. `[factory:mac-2]`) | | `budget` | RESERVED | _(none)_ | `{ usd, tokens, wall }` ceilings (`wall` enforcement is a later slice) | -| `deps` / `deps-mode` | RESERVED | _(none)_ | DAG dependencies (single-host blocking is a later slice) | +| `deps` / `deps-mode` | **active** | _(none)_ | block until each referenced `idempotency-key` is in `shipped/` (or `testing/` when `deps-mode: soft`). Submit-time cycle detection (see **Profiles & deps**) | | `retry` | **active** | _(none)_ | `{ max: N, backoff: 5m, on: [timeout, verify_failed, crash] }` — requeue failures with backoff up to `max`, then `retries_exhausted` (see **Resilience**) | | `review-policy` | RESERVED | _(none)_ | `auto\|manual\|reviewers:[…]` | | `artifacts` | RESERVED | _(none)_ | extra outputs to capture (coverage, screenshots) | @@ -232,6 +232,42 @@ queue/ (transient: requeued for another attempt), `recovered` (transient: an orphan was reclaimed to `inbox/`). +## Profiles & deps + +### Profiles (roadmap §6) + +A **profile** is a reusable role preset in `profiles/.md`. A job opts in with +`profile: ` and inherits any of these fields it does **not** set itself: +`verify` (from the profile's `default-verify`), `capabilities`, `engine-class`, +`prefers-engine`, `allowed-scope`, `review-policy`. The profile's `persona` block is +**prepended** to the body sent to the engine (the job `.md` on disk is unchanged; +secrets are never logged). Resolution runs **before** the capability gate and engine +resolution, so inherited caps / engine-class take effect. + +**Precedence:** `job field > profile field > built-in default`. Set `AGENT_QUEUE_PROFILES` +to point at a different catalog directory (defaults to `./profiles`). + +Starter catalog: `developer`, `backend-engineer`, `frontend-engineer`, `ux-designer`, +`ui-designer`, `qa`, `reviewer`, `docs-writer`, and a reserved `planner`. Each presets +`name`, `persona`, `capabilities`, `default-verify`, `engine-class`, `prefers-engine`, +`allowed-scope`, and `review-policy`. + +**allowed-scope (warn-only this phase).** After a run on a git `cwd`, changed paths +outside the profile/job `allowed-scope` globs (`dir/**` matches the whole subtree) are +logged as a `WARNING` and recorded as `scope_warning=` in the meta — **non-blocking** +(the job is not failed). `path_in_scope` is exposed as a unit-testable function. + +### deps / DAG, single host (roadmap §5) + +`deps: [keyA, keyB]` references other jobs by their author-controlled +`idempotency-key`. A dep is **satisfied** when a job with that key is in `shipped/` +(default), or in `shipped/` **or** `testing/` when the dependent job sets +`deps-mode: soft`. A job with unmet deps is **blocked**: it is skipped in inbox +selection (never launched, never failed) and surfaced in `status` as +`blocked (waiting on: )`, then re-evaluated every loop until its deps are met. +`add` performs **submit-time cycle detection** over the inbox + active-stage dep graph +and rejects (nonzero exit) a job that would create a cycle. Cross-machine deps are P2. + ## Resilience (crash recovery & work preservation) Single-host implementations of the durability model (roadmap §25): diff --git a/agent-queue/docs/GIGAFACTORY_ROADMAP.md b/agent-queue/docs/GIGAFACTORY_ROADMAP.md index f202805..d49dd0a 100644 --- a/agent-queue/docs/GIGAFACTORY_ROADMAP.md +++ b/agent-queue/docs/GIGAFACTORY_ROADMAP.md @@ -11,7 +11,7 @@ | Phase | Theme | Status | % | Gate | | ----- | ----- | ------ | - | ---- | | **0** | Baseline (today) | ✅ shipped | 100% | `selftest.sh` green | -| **1** | Manifest + profiles + capabilities + tracker adapter (single host) | ◐ in progress | 55% | adapter e2e + selftest | +| **1** | Manifest + profiles + capabilities + tracker adapter (single host) | ◐ in progress | 80% | adapter e2e + selftest | | **2** | Coordinator as platform-service module + Cosmos + multi-factory leasing | ☐ not started | 0% | fleet e2e + module tests | | **3** | Fleet control plane in tracker-web + DAG deps + budgets + scoring router | ☐ not started | 0% | web e2e + router tests | | **4** | Message bus + autoscaling + cross-OS capability marketplace | ☐ not started | 0% | load/chaos suite | @@ -142,7 +142,7 @@ tracker-item: ITEM-789 # link back to the originating tracker task - [x] **Capability grammar** defined: tokens are `key` (presence, e.g. `has:xcode`), `key:value` (e.g. `os:mac`, `engine:devin`), or `keyversion` with `op ∈ {>=,>,=,<=,<}` (e.g. `node>=20`). `os:any` is a wildcard that matches every factory. A job matches a factory iff every required token is satisfied by the factory descriptor. *(P1-S1: `caps_match`/`detect_capabilities` in `agent-queue.sh`.)* - [x] **`engine-class` taxonomy** defined as an enum (`agentic-coder`, `chat-coder`, `review-only`) with a documented engine→class map (`devin,claude,codex → agentic-coder`; `copilot → chat-coder`). If `engine` is set it wins; else the scheduler picks any free engine in the class honoring `prefers-engine`. *(P1-S1: `resolve_engine`; `review-only` mapping reserved.)* - [x] **`idempotency-key` semantics:** `key + content-hash` identical ⇒ no-op (returns existing job). Same `key`, **different** content ⇒ **rejected with 409** unless the prior job is still `queued`/`blocked` (then it is superseded). A re-`run`/`retry` of an existing job is **not** a new submit and never trips dedupe. *(P1-S1: add-time dedupe; bash maps "409" → clear error, `queued` → still in `inbox/` ⇒ superseded.)* -- [ ] **`deps` semantics:** a dep is satisfied when it reaches `shipped` (default) or `testing` if `deps-mode: soft`. Submit-time **cycle detection** rejects cyclic graphs; unmet deps put the job in `blocked` (not `queued`). Cross-factory deps require the coordinator (P2); single-host deps work in P1. +- [x] **`deps` semantics:** a dep is satisfied when it reaches `shipped` (default) or `testing` if `deps-mode: soft`. Submit-time **cycle detection** rejects cyclic graphs; unmet deps put the job in `blocked` (not `queued`). Cross-factory deps require the coordinator (P2); single-host deps work in P1. *(P1-S2: `deps_unmet` skip-with-reason in selection + `status` surfacing; `deps_would_cycle` on `add`. Cross-machine deps remain P2.)* - **Acceptance:** a manifest fixture suite parses/validates; invalid manifests fail with precise errors; capability-grammar + dep-cycle + idempotency-conflict cases covered. - **Verify gate:** schema unit tests (≥ 1 per field incl. defaults + 5 invalid cases + grammar/cycle/409 cases). @@ -167,11 +167,11 @@ review-policy: manual --- ``` -- [ ] Author starter catalog: `developer`, `backend-engineer`, `frontend-engineer`, `ux-designer`, `ui-designer`, `qa`, `reviewer`, `docs-writer`. -- [ ] Persona overlay is **prepended** to the job body before the agent runs; secrets are never written to logs or the event stream (redaction at the source). -- [ ] Profile supplies default `verify`, `capabilities`, `engine-class`, `allowed-scope` when the job omits them. -- [ ] Profile versioning: changing a profile doesn't mutate in-flight jobs (snapshot at assign time). -- [ ] `allowed-scope` enforced as a guardrail (warn in P1, enforce/deny in P2 via pre-flight diff check). +- [x] Author starter catalog: `developer`, `backend-engineer`, `frontend-engineer`, `ux-designer`, `ui-designer`, `qa`, `reviewer`, `docs-writer`. *(P1-S2: `profiles/*.md` + a reserved `planner`.)* +- [x] Persona overlay is **prepended** to the job body before the agent runs; secrets are never written to logs or the event stream (redaction at the source). *(P1-S2: `profile_persona` prepended to the stripped body file.)* +- [x] Profile supplies default `verify`, `capabilities`, `engine-class`, `allowed-scope` when the job omits them. *(P1-S2: `fm_eff` — also `prefers-engine` + `review-policy`; job fields always override.)* +- [ ] Profile versioning: changing a profile doesn't mutate in-flight jobs (snapshot at assign time). *(P2 — needs Cosmos snapshot at assign time.)* +- [x] `allowed-scope` enforced as a guardrail (warn in P1, enforce/deny in P2 via pre-flight diff check). *(P1-S2: `scope_check` post-run WARN-only + `scope_warning=` in meta; `path_in_scope` unit-testable.)* - **Acceptance:** a job with `profile: backend-engineer` and no `verify` inherits the profile's verify + persona. - **Verify gate:** profile-resolution unit tests; persona-injection golden test. @@ -342,18 +342,20 @@ Each phase: **Goal → checklist → Exit criteria**. Don't start a phase until > **Slice progress — P1-S1:** manifest parsing (all §5 fields, defaulted + backward-compatible), `priority` ordering, capability detection+match gate, `engine-class` resolution, and `idempotency-key` dedupe are **done** on the bash runner. > -> **Slice progress — P1-S3 (resilience & insights, single host):** crash recovery (`recover_orphans` + `aq recover`), git WIP checkpoint/resume (`aq/wip/`), functional `retry` policy (backoff + `retries_exhausted`), and execution insights (`parse_usage`, per-run metrics in meta, `aq insights`, `status`/`dash` insights) are **done** — see §11/§25/§26. Profiles, `deps` DAG, `budget.wall`, `allowed-scope`, and the tracker adapter remain **for later slices**. +> **Slice progress — P1-S3 (resilience & insights, single host):** crash recovery (`recover_orphans` + `aq recover`), git WIP checkpoint/resume (`aq/wip/`), functional `retry` policy (backoff + `retries_exhausted`), and execution insights (`parse_usage`, per-run metrics in meta, `aq insights`, `status`/`dash` insights) are **done** — see §11/§25/§26. +> +> **Slice progress — P1-S2 (profiles + deps/DAG, single host):** the `profiles/` catalog + resolution (`fm_eff` inheritance with job>profile>default precedence, persona injection), the warn-only `allowed-scope` guardrail (`scope_check`/`path_in_scope`), and single-host `deps` (block-with-reason in selection, `status` surfacing, submit-time cycle detection) are **done** — see §5/§6. The tracker adapter and `budget.wall` remain **for later slices**. - [x] Extend `agent-queue.sh` frontmatter parsing for all new manifest fields (§5), defaulted + backward-compatible. *(P1-S1)* -- [ ] Add `profiles/` directory + profile resolution (persona injection, default verify/caps/scope) (§6). +- [x] Add `profiles/` directory + profile resolution (persona injection, default verify/caps/scope) (§6). *(P1-S2)* - [x] Local capability detection + a job/factory capability match check before launch (§8 subset). *(P1-S1: `detect_capabilities` + `caps_match`; mismatch ⇒ `failed/` `result=capability_mismatch`, agent never launched.)* - [x] `priority` ordering in the inbox pick (replace pure FIFO with priority-then-age). *(P1-S1: `inbox_sorted`; per-lock serialization preserved.)* -- [ ] `deps` (DAG) blocking on a single host; `idempotency-key` dedupe on `add`. *(P1-S1: `idempotency-key` dedupe DONE; `deps` DAG blocking still pending.)* +- [x] `deps` (DAG) blocking on a single host; `idempotency-key` dedupe on `add`. *(P1-S1 idempotency dedupe + P1-S2 `deps` blocking/cycle detection.)* - [ ] `retry` with backoff into `failed`/requeue; `budget.wall` enforced (extends `timeout`). *(P1-S3: `retry` with backoff + `retries_exhausted` DONE; `budget.wall` still pending.)* -- [ ] `allowed-scope` guardrail (warn-only this phase) + post-run diff report. +- [x] `allowed-scope` guardrail (warn-only this phase) + post-run diff report. *(P1-S2: `scope_check` WARN-only + `scope_warning=`.)* - [ ] **Tracker adapter** `aq from-tracker ` + `aq to-tracker` event poster (§10 P1). - [ ] Dashboard shows profile + priority + capability tags + tracker-item link. *(P1-S1: `status` shows priority/profile/caps/tracker-item; Node `dash` surfacing pending.)* -- [ ] Update `selftest.sh` with: manifest parse fixtures, profile resolution, priority order, dep-block, idempotency, adapter round-trip (mock). *(P1-S1: added backward-compat, priority, capability-mismatch, engine-class, idempotency cases; profile/dep-block/adapter pending.)* +- [ ] Update `selftest.sh` with: manifest parse fixtures, profile resolution, priority order, dep-block, idempotency, adapter round-trip (mock). *(P1-S1 manifest/priority/idempotency + P1-S2 profile resolution/persona/scope/dep-block/cycle + P1-S3 resilience/insights; tracker adapter round-trip still pending.)* - [x] Update README + this doc's progress table. *(P1-S1)* - **Exit criteria:** all boxes ✅; `selftest.sh` green; a tracker task → executed → tracker `done` with SHA comment, fully on one host; no regression to Phase-0 `.md` files.