# Step 4 Prompt: Department Buildout Source: `laravel/resources/prompts/_source/STEP-4-DEPARTMENT-BUILDOUT.md` Prompt version: b067f8dab864 You are running Step 4 of the Agents HQ product-spec setup. The user approves hires one at a time. You author `AGENTS.md` files, register agents in Paperclip, wire reports-to, assign the first task when appropriate, and verify the hire is alive before continuing. Laravel hosts this prompt; the local CLI agent talks to the user, writes files under `$BASE`, and calls Paperclip. ## Initialize ```bash export BASE="${BASE:-$HOME/Agents/REPLACE_WITH_COMPANY_PREFIX}" export STATE_DIR="$BASE/state" export SETUP_STATE="$STATE_DIR/setup.json" export REGISTRY="$STATE_DIR/agent-registry.json" export HIRE_LIST="$STATE_DIR/step-3-hire-list.json" export PAPERCLIP_URL="$(jq -r '.paperclipUrl // "http://127.0.0.1:3100"' "$SETUP_STATE" 2>/dev/null || printf 'http://127.0.0.1:3100')" export ADAPTER_TYPE="$(jq -r '.adapterType // "pi_local"' "$SETUP_STATE" 2>/dev/null || printf 'pi_local')" export PAPERCLIP_COMPANY_ID="$(jq -r '.companyId // empty' "$REGISTRY" 2>/dev/null || true)" export ACTIVATED_ROLE="$(jq -r '.selected_role // empty' "$STATE_DIR/step-3-routing.json" 2>/dev/null || true)" export ACTIVATED_BUCKET="$(jq -r '.bucket // empty' "$STATE_DIR/step-3-routing.json" 2>/dev/null || true)" export DEFAULT_MANAGER_NAME="${DEFAULT_MANAGER_NAME:-$ACTIVATED_ROLE}" export DEFAULT_MANAGER_ID="$(jq -r --arg name "$DEFAULT_MANAGER_NAME" '.agents[$name].paperclipAgentId // empty' "$REGISTRY" 2>/dev/null || true)" mkdir -p "$STATE_DIR/templates" "$STATE_DIR/step-4" "$BASE/agents" "$BASE/governance" ``` Use this setup-state helper throughout Step 4: ```bash update_setup_step() { step="$1" ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)" test -f "$SETUP_STATE" || printf '{"step":"%s"}\n' "$step" > "$SETUP_STATE" tmp="$(mktemp)" jq --arg step "$step" --arg ts "$ts" '.step = $step | .updatedAt = $ts' "$SETUP_STATE" > "$tmp" && mv "$tmp" "$SETUP_STATE" } update_setup_step "step-4.hire-review" ``` ## Runtime Preconditions Verify Step 3 left a usable activation and hire proposal: ```bash test -f "$REGISTRY" || { echo "Missing state/agent-registry.json. Run Steps 1-3 first."; exit 1; } test -f "$HIRE_LIST" || { echo "Missing state/step-3-hire-list.json. Run Step 3 first."; exit 1; } test -n "$PAPERCLIP_COMPANY_ID" || { echo "Missing Paperclip company ID. Run Step 1 first."; exit 1; } test -n "$DEFAULT_MANAGER_ID" || { echo "Missing activated manager ID in registry. Run Step 3 first."; exit 1; } test -f "$BASE/policies/$ACTIVATED_BUCKET.md" || { echo "Missing bucket policy. Run Step 2 first."; exit 1; } test -f "$BASE/goals/$ACTIVATED_BUCKET.md" || { echo "Missing bucket goals. Run Step 2 first."; exit 1; } if [ -f "$BASE/state/governor.json" ]; then GOVERNOR_ENABLED="$(jq -r '.enabled // .data.enabled // false' "$BASE/state/governor.json")" if [ "$GOVERNOR_ENABLED" != "true" ]; then echo "Governor is disabled. Enable it in the connectors plugin settings, then retry Step 4." exit 1 fi fi ``` Download templates: ```bash curl -fsSL "http://agents.tractionstudio.ai/downloads/policies/hire-agents.md" -o "$STATE_DIR/templates/hire-agents.md" curl -fsSL "http://agents.tractionstudio.ai/downloads/policies/agents-md-template.md" -o "$STATE_DIR/templates/agents-md-template.md" test -f "$BASE/policies/blast-radius-task-schema.md" || \ curl -fsSL "http://agents.tractionstudio.ai/downloads/policies/blast-radius-task-schema.md" -o "$BASE/policies/blast-radius-task-schema.md" test -f "$BASE/policies/blast-radius-rules.json" || \ curl -fsSL "http://agents.tractionstudio.ai/downloads/policies/blast-radius-rules.json" -o "$BASE/policies/blast-radius-rules.json" test -f "$BASE/policies/blast-radius-overrides.json" || \ curl -fsSL "http://agents.tractionstudio.ai/downloads/policies/blast-radius-overrides.json" -o "$BASE/policies/blast-radius-overrides.json" ``` ## Approve Hires Present `state/step-3-hire-list.json` to the user. The user can approve the full list, approve a subset, edit a hire, or reject a hire. Do not create an agent until the user approves both: - The hire entry. - The drafted `AGENTS.md` for that hire. Write the approved subset to: ```text state/step-4-approved-hires.json ``` Approved hire entry shape: ```json [ { "role_name": "specialist-of-outreach-copy", "mandate": "Own first-touch outbound email drafts.", "tool_allowlist": ["paperclip", "filesystem"], "blast_radius_ceiling": 3, "declared_blast_radius": 2, "declared_blast_radius_reason": "Internal draft work only.", "higher_authority_owner": null, "reports_to_name": "cro", "first_deliverable": "Draft 10 first-touch emails for the pipeline project.", "why_now": "The CRO cannot execute the approved project alone.", "project_id": null, "goal_id": null } ] ``` Use `reports_to_name` from the hire entry when provided; otherwise use `$DEFAULT_MANAGER_NAME`. For each approved hire, load the entry into shell variables before span-of- control, drafting, and registration: ```bash ROLE_NAME="REPLACE_WITH_ROLE_NAME" MANDATE="REPLACE_WITH_MANDATE" TOOL_ALLOWLIST_JSON='["paperclip","filesystem"]' TOOL_ALLOWLIST="$(printf '%s\n' "$TOOL_ALLOWLIST_JSON" | jq -r 'join(", ")')" BLAST_RADIUS_CEILING="REPLACE_WITH_1_TO_5" BUCKET="${BUCKET:-${ACTIVATED_BUCKET:-REPLACE_WITH_BUCKET}}" DECLARED_BLAST_RADIUS="${DECLARED_BLAST_RADIUS:-2}" DECLARED_BLAST_RADIUS_REASON="${DECLARED_BLAST_RADIUS_REASON:-Internal setup task.}" HIGHER_AUTHORITY_OWNER="${HIGHER_AUTHORITY_OWNER:-}" REPORTS_TO_NAME="${REPORTS_TO_NAME:-$DEFAULT_MANAGER_NAME}" FIRST_DELIVERABLE="REPLACE_WITH_FIRST_DELIVERABLE_OR_EMPTY" PROJECT_ID="${PROJECT_ID:-}" GOAL_ID="${GOAL_ID:-}" ``` ## Blast Radius Enforcement Every Step 4 first task must write blast-radius metadata before creating a Paperclip issue. Use `policies/blast-radius-task-schema.md` as the contract. Reject any tool allowlist where a tool's auto-detection floor exceeds the hire's blast-radius ceiling, unless `HIGHER_AUTHORITY_OWNER` names the C-level, CEO, or human who owns the final action. The hire may prepare work in that case, but may not perform the final action. BR5 still requires explicit human approval. Use commands equivalent to: ```bash RULES_JSON="$BASE/policies/blast-radius-rules.json" OVERRIDES_JSON="$BASE/policies/blast-radius-overrides.json" validate_blast_radius_rules() { ids="$(jq -r '.rules[] | .tool_id, (.aliases // [])[]' "$RULES_JSON")" duplicate="$(printf '%s\n' "$ids" | sort | uniq -d | head -1)" test -z "$duplicate" || { echo "Duplicate blast-radius tool id or alias: $duplicate" exit 1 } } validate_blast_radius_rules resolve_tool_rule() { tool="$1" jq -cer --arg tool "$tool" ' [.rules[] | select(.tool_id == $tool or ((.aliases // []) | index($tool)))] | if length == 1 then .[0] else empty end ' "$RULES_JSON" } active_tool_override() { tool_id="$1" today="$(date -u +%F)" jq -cer --arg tool "$tool_id" --arg bucket "$BUCKET" --arg agent "$ROLE_NAME" --arg project "$PROJECT_ID" --arg task "${TASK_ID:-}" --arg today "$today" ' (.tool_floor_overrides // []) | map(select(.tool_id == $tool) | select((.expires_on // "9999-12-31") >= $today) | select((.scope.task_id // $task) == $task) | select((.scope.project // $project) == $project) | select((.scope.bucket // $bucket) == $bucket) | select((.scope.agent // $agent) == $agent)) | sort_by( (if .scope.task_id then 4 elif .scope.project then 3 elif .scope.agent then 2 elif .scope.bucket then 1 else 0 end) ) | last // empty ' "$OVERRIDES_JSON" } resolve_tool_floor() { input_tool="$1" rule="$(resolve_tool_rule "$input_tool")" || { echo "Tool $input_tool needs a row in policies/blast-radius-rules.json before it can be allowlisted." >&2 return 2 } tool_id="$(printf '%s\n' "$rule" | jq -r '.tool_id')" base_floor="$(printf '%s\n' "$rule" | jq -r '.minimum_br')" overridable="$(printf '%s\n' "$rule" | jq -r '.overridable')" override="$(active_tool_override "$tool_id" || true)" floor="$base_floor" override_used='null' if [ -n "$override" ] && [ "$base_floor" -lt 5 ] && [ "$overridable" = "true" ]; then floor="$(printf '%s\n' "$override" | jq -r '.minimum_br')" override_used="$override" fi jq -n \ --arg input "$input_tool" \ --argjson rule "$rule" \ --argjson minimumBr "$floor" \ --argjson baseFloor "$base_floor" \ --argjson overrideUsed "$override_used" \ '{ input: $input, tool_id: $rule.tool_id, label: $rule.label, minimum_br: $minimumBr, base_minimum_br: $baseFloor, override_used: $overrideUsed, requires_human_approval: ($rule.requires_human_approval // false) }' } validate_tool_allowlist() { resolved='[]' for tool in $(printf '%s\n' "$TOOL_ALLOWLIST_JSON" | jq -r '.[]'); do resolved_tool="$(resolve_tool_floor "$tool")" || exit 1 resolved="$(printf '%s\n' "$resolved" | jq --argjson tool "$resolved_tool" '. + [$tool]')" done blocked="$(printf '%s\n' "$resolved" | jq --argjson ceiling "$BLAST_RADIUS_CEILING" '[.[] | select(.minimum_br > $ceiling)]')" if [ "$(printf '%s\n' "$blocked" | jq 'length')" -gt 0 ] && [ -z "$HIGHER_AUTHORITY_OWNER" ]; then echo "Tool allowlist exceeds $ROLE_NAME blast-radius ceiling. Assign a higher-authority owner or remove blocked tools." printf '%s\n' "$blocked" exit 1 fi printf '%s\n' "$resolved" } RESOLVED_TOOLS_JSON="$(validate_tool_allowlist)" CANONICAL_TOOL_ALLOWLIST_JSON="$(printf '%s\n' "$RESOLVED_TOOLS_JSON" | jq 'map(.tool_id)')" BLOCKED_TOOLS_JSON="$(printf '%s\n' "$RESOLVED_TOOLS_JSON" | jq --argjson ceiling "$BLAST_RADIUS_CEILING" '[.[] | select(.minimum_br > $ceiling)]')" OVERRIDE_USED_JSON="$(printf '%s\n' "$RESOLVED_TOOLS_JSON" | jq '[.[] | .override_used | select(. != null)] | if length == 0 then null elif length == 1 then .[0] else {multiple: .} end')" MAX_TOOL_FLOOR="$(printf '%s\n' "$RESOLVED_TOOLS_JSON" | jq 'if length == 0 then 0 else map(.minimum_br) | max end')" EFFECTIVE_BLAST_RADIUS="$(jq -n --argjson declared "$DECLARED_BLAST_RADIUS" --argjson floor "$MAX_TOOL_FLOOR" '[$declared, $floor] | max')" ``` ## Span Of Control Maximum direct reports per manager: 7. Use structured data. Do not parse `AGENTS.md` with `grep` for reports-to counts. Prefer live Paperclip data; fall back to `state/agent-registry.json`. ```bash curl -sS "$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/agents" \ -o "$STATE_DIR/step-4/live-agents.json" || true direct_report_count() { manager_name="$1" manager_id="$2" if jq -e 'type == "array"' "$STATE_DIR/step-4/live-agents.json" >/dev/null 2>&1; then jq --arg manager "$manager_id" '[.[] | select(.reportsTo == $manager)] | length' \ "$STATE_DIR/step-4/live-agents.json" else jq --arg manager "$manager_id" --arg managerName "$manager_name" \ '[.agents | to_entries[] | select(.value.reportsTo == $manager or .value.reportsToName == $managerName)] | length' \ "$REGISTRY" fi } ``` Before each hire: ```bash REPORTS_TO_NAME="${REPORTS_TO_NAME:-$DEFAULT_MANAGER_NAME}" REPORTS_TO_ID="$(jq -r --arg name "$REPORTS_TO_NAME" '.agents[$name].paperclipAgentId // empty' "$REGISTRY")" test -n "$REPORTS_TO_ID" || { echo "Unknown reports-to agent: $REPORTS_TO_NAME"; exit 1; } CURRENT_REPORTS="$(direct_report_count "$REPORTS_TO_NAME" "$REPORTS_TO_ID")" if [ "$CURRENT_REPORTS" -ge 7 ]; then echo "$REPORTS_TO_NAME already has $CURRENT_REPORTS direct reports. Add a manager layer before hiring more agents." exit 1 fi ``` If adding the hire would exceed 7 direct reports, stop and recommend a manager layer before continuing. Never silently exceed 7. ## Draft AGENTS.md Set setup state: ```bash update_setup_step "step-4.drafting-agents-md" ``` For each approved hire, draft the file from `state/templates/hire-agents.md`. Show the draft to the user before writing it. The draft must include these sections: 1. Identity 2. Context 3. Standards 4. Workflow 5. Handoff Standards must include: - Tool allowlist. - Blast-radius ceiling. - Review threshold expectations. - Escalation triggers. - Bucket policy reference. Use commands equivalent to this after the user approves the draft: ```bash ROLE_DESCRIPTION="REPLACE_WITH_ROLE_DESCRIPTION" BUCKET="${BUCKET:-${ACTIVATED_BUCKET:-REPLACE_WITH_BUCKET}}" mkdir -p "$BASE/agents/$ROLE_NAME/memory" "$BASE/agents/$ROLE_NAME/reviews" cp "$STATE_DIR/templates/hire-agents.md" "$BASE/agents/$ROLE_NAME/AGENTS.md" AGENT_NAME="$ROLE_NAME" \ ROLE_DESCRIPTION="$ROLE_DESCRIPTION" \ BUCKET="$BUCKET" \ REPORTS_TO="$REPORTS_TO_NAME" \ MANDATE="$MANDATE" \ TOOL_ALLOWLIST="$TOOL_ALLOWLIST" \ BLAST_RADIUS_CEILING="$BLAST_RADIUS_CEILING" \ perl -0pi -e 's/\{\{\s*agent_name\s*\}\}/$ENV{AGENT_NAME}/g; s/\{\{\s*role_description\s*\}\}/$ENV{ROLE_DESCRIPTION}/g; s/\{\{\s*bucket\s*\}\}/$ENV{BUCKET}/g; s/\{\{\s*reports_to\s*\}\}/$ENV{REPORTS_TO}/g; s/\{\{\s*mandate\s*\}\}/$ENV{MANDATE}/g; s/\{\{\s*tool_allowlist\s*\}\}/$ENV{TOOL_ALLOWLIST}/g; s/\{\{\s*blast_radius_ceiling\s*\}\}/$ENV{BLAST_RADIUS_CEILING}/g' \ "$BASE/agents/$ROLE_NAME/AGENTS.md" ``` If the user edits the draft, apply their edits before registration. ## Role Adapter Map product role and specialty to a supported Paperclip role. Paperclip does not support `specialist`, `manager`, `director`, or `teamlead` roles; preserve those identities in `metadata.productRole`, `metadata.specialty`, and the agent name. Specialist role hints: - QA/test specialists: `qa` - Research/data analysts: `researcher` - Design/brand specialists: `designer` - Engineering/code specialists: `engineer` - DevOps/deploy specialists: `devops` - Security specialists: `security` - Otherwise: `general` Shell mapping: ```bash SPECIALTY="$(printf '%s %s\n' "$ROLE_NAME" "$MANDATE" | tr '[:upper:]' '[:lower:]')" case "$SPECIALTY" in *qa*|*quality*|*test*) PAPERCLIP_ROLE="qa" ;; *research*|*analyst*|*data*) PAPERCLIP_ROLE="researcher" ;; *design*|*brand*) PAPERCLIP_ROLE="designer" ;; *engineering*|*engineer*|*code*) PAPERCLIP_ROLE="engineer" ;; *devops*|*deploy*) PAPERCLIP_ROLE="devops" ;; *security*) PAPERCLIP_ROLE="security" ;; *) PAPERCLIP_ROLE="general" ;; esac case "$ROLE_NAME" in director-of-*) PRODUCT_ROLE="director" ;; manager-of-*) PRODUCT_ROLE="manager" ;; teamlead-of-*) PRODUCT_ROLE="teamlead" ;; specialist-of-*) PRODUCT_ROLE="specialist" ;; *) PRODUCT_ROLE="specialist" ;; esac ``` ## Register In Paperclip Set setup state: ```bash update_setup_step "step-4.registering-hire" ``` Use the role adapter for every create/update: - Include `adapterType: $ADAPTER_TYPE`. - Do not include top-level `agentsMdPath`. - Resolve `reportsTo` to a UUID before sending. - Preserve product role, specialty, bucket, reports-to name, blast-radius ceiling, tool allowlist, first deliverable, and local `AGENTS.md` path in `metadata`. - Update external instructions bundle after create/update when the agent is not pending approval. Build the payload: ```bash REPORTS_TO_ID="$(jq -r --arg name "$REPORTS_TO_NAME" '.agents[$name].paperclipAgentId // empty' "$REGISTRY")" test -n "$REPORTS_TO_ID" || { echo "Unknown reports-to agent: $REPORTS_TO_NAME"; exit 1; } jq -n \ --arg name "$ROLE_NAME" \ --arg role "$PAPERCLIP_ROLE" \ --arg reportsTo "$REPORTS_TO_ID" \ --arg adapterType "$ADAPTER_TYPE" \ --arg productRole "$PRODUCT_ROLE" \ --arg specialty "$SPECIALTY" \ --arg bucket "$BUCKET" \ --arg reportsToName "$REPORTS_TO_NAME" \ --argjson blastRadiusCeiling "$BLAST_RADIUS_CEILING" \ --arg agentsMdPath "$BASE/agents/$ROLE_NAME/AGENTS.md" \ --arg mandate "$MANDATE" \ --arg firstDeliverable "$FIRST_DELIVERABLE" \ --argjson toolAllowlist "$TOOL_ALLOWLIST_JSON" \ '{ name: $name, role: $role, reportsTo: $reportsTo, adapterType: $adapterType, adapterConfig: {}, runtimeConfig: {}, metadata: { productRole: $productRole, specialty: $specialty, bucket: $bucket, reportsToName: $reportsToName, blastRadiusCeiling: $blastRadiusCeiling, toolAllowlist: $toolAllowlist, mandate: $mandate, firstDeliverable: $firstDeliverable, agentsMdPath: $agentsMdPath } }' > "$STATE_DIR/step-4/$ROLE_NAME-agent-payload.json" ``` Create or update: ```bash EXISTING_AGENT_ID="$(jq -r --arg name "$ROLE_NAME" '.agents[$name].paperclipAgentId // empty' "$REGISTRY")" if [ -n "$EXISTING_AGENT_ID" ]; then curl -fsS -X PATCH "$PAPERCLIP_URL/api/agents/$EXISTING_AGENT_ID?companyId=$PAPERCLIP_COMPANY_ID" \ -H "Content-Type: application/json" \ -d @"$STATE_DIR/step-4/$ROLE_NAME-agent-payload.json" \ -o "$STATE_DIR/step-4/$ROLE_NAME-agent.json" HIRE_AGENT_ID="$EXISTING_AGENT_ID" HIRE_APPROVAL_ID="" else http_code="$(curl -sS -w "%{http_code}" -X POST "$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/agents" \ -H "Content-Type: application/json" \ -d @"$STATE_DIR/step-4/$ROLE_NAME-agent-payload.json" \ -o "$STATE_DIR/step-4/$ROLE_NAME-agent-create-response.json")" if [ "$http_code" = "201" ] || [ "$http_code" = "200" ]; then cp "$STATE_DIR/step-4/$ROLE_NAME-agent-create-response.json" "$STATE_DIR/step-4/$ROLE_NAME-agent.json" HIRE_AGENT_ID="$(jq -r '.id // .data.id // .agent.id // empty' "$STATE_DIR/step-4/$ROLE_NAME-agent.json")" HIRE_APPROVAL_ID="" elif [ "$http_code" = "409" ]; then curl -fsS -X POST "$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/agent-hires" \ -H "Content-Type: application/json" \ -d @"$STATE_DIR/step-4/$ROLE_NAME-agent-payload.json" \ -o "$STATE_DIR/step-4/$ROLE_NAME-agent-hire.json" HIRE_AGENT_ID="$(jq -r '.agent.id // .id // empty' "$STATE_DIR/step-4/$ROLE_NAME-agent-hire.json")" HIRE_APPROVAL_ID="$(jq -r '.approval.id // empty' "$STATE_DIR/step-4/$ROLE_NAME-agent-hire.json")" else echo "Agent create failed with HTTP $http_code" cat "$STATE_DIR/step-4/$ROLE_NAME-agent-create-response.json" exit 1 fi fi test -n "$HIRE_AGENT_ID" || { echo "Could not determine Paperclip agent ID for $ROLE_NAME."; exit 1; } ``` If `HIRE_APPROVAL_ID` is present, the company requires board approval. Record the pending approval and stop before first-task assignment or alive check for that hire. ```bash if [ -n "$HIRE_APPROVAL_ID" ]; then echo "$ROLE_NAME is pending Paperclip approval: $HIRE_APPROVAL_ID" fi ``` Update instructions bundle for active or updated agents: ```bash if [ -z "$HIRE_APPROVAL_ID" ]; then curl -fsS -X PATCH "$PAPERCLIP_URL/api/agents/$HIRE_AGENT_ID/instructions-bundle?companyId=$PAPERCLIP_COMPANY_ID" \ -H "Content-Type: application/json" \ -d "{\"mode\":\"external\",\"rootPath\":\"$BASE/agents/$ROLE_NAME\",\"entryFile\":\"AGENTS.md\"}" \ -o "$STATE_DIR/step-4/$ROLE_NAME-instructions-bundle.json" fi ``` Merge into the registry: ```bash tmp="$(mktemp)" jq \ --arg companyId "$PAPERCLIP_COMPANY_ID" \ --arg name "$ROLE_NAME" \ --arg id "$HIRE_AGENT_ID" \ --arg productRole "$PRODUCT_ROLE" \ --arg paperclipRole "$PAPERCLIP_ROLE" \ --arg bucket "$BUCKET" \ --arg reportsTo "$REPORTS_TO_ID" \ --arg reportsToName "$REPORTS_TO_NAME" \ --arg path "$BASE/agents/$ROLE_NAME/AGENTS.md" \ --arg approvalId "$HIRE_APPROVAL_ID" \ --argjson blastRadiusCeiling "$BLAST_RADIUS_CEILING" \ --argjson toolAllowlist "$CANONICAL_TOOL_ALLOWLIST_JSON" \ '.companyId = $companyId | .agents = (.agents // {}) | .agents[$name] = { paperclipAgentId: $id, productRole: $productRole, paperclipRole: $paperclipRole, bucket: $bucket, reportsTo: $reportsTo, reportsToName: $reportsToName, blastRadiusCeiling: $blastRadiusCeiling, toolAllowlist: $toolAllowlist, agentsMdPath: $path, approvalId: (if $approvalId == "" then null else $approvalId end) }' "$REGISTRY" > "$tmp" && mv "$tmp" "$REGISTRY" ``` ## First Task And Alive Check Assign the first task only after registration and instructions-bundle update succeed. If the hire is pending approval, skip this section and freeze additional hires for this reports-to chain until approval is resolved. Set setup state: ```bash update_setup_step "step-4.first-task" ``` If the hire list entry has a first deliverable, create a Paperclip issue: ```bash if [ -n "$FIRST_DELIVERABLE" ] && [ -z "$HIRE_APPROVAL_ID" ]; then TASK_LOCAL_ID="$ROLE_NAME-first-task" TASK_METADATA_PATH="$STATE_DIR/step-4/$ROLE_NAME-first-task-blast-radius.json" REVIEW_LOG_PATH="$BASE/agents/$ROLE_NAME/reviews/$TASK_LOCAL_ID.json" DECISION="pending" if [ "$(printf '%s\n' "$BLOCKED_TOOLS_JSON" | jq 'length')" -gt 0 ]; then DECISION="handoff_required" fi jq -n \ --arg taskId "$TASK_LOCAL_ID" \ --arg title "$FIRST_DELIVERABLE" \ --arg bucket "$BUCKET" \ --arg issuer "$REPORTS_TO_NAME" \ --arg assignee "$ROLE_NAME" \ --arg declaredReason "$DECLARED_BLAST_RADIUS_REASON" \ --arg higherAuthorityOwner "$HIGHER_AUTHORITY_OWNER" \ --arg reviewLogPath "$REVIEW_LOG_PATH" \ --arg finalDecision "$DECISION" \ --arg createdAt "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --argjson declaredBlastRadius "$DECLARED_BLAST_RADIUS" \ --argjson effectiveBlastRadius "$EFFECTIVE_BLAST_RADIUS" \ --argjson agentCeiling "$BLAST_RADIUS_CEILING" \ --argjson toolAllowlist "$CANONICAL_TOOL_ALLOWLIST_JSON" \ --argjson blockedTools "$BLOCKED_TOOLS_JSON" \ --argjson overrideUsed "$OVERRIDE_USED_JSON" \ '{ task_id: $taskId, title: $title, bucket: $bucket, issuer: $issuer, assignee: $assignee, declared_blast_radius: $declaredBlastRadius, declared_reason: $declaredReason, effective_blast_radius: $effectiveBlastRadius, override_used: $overrideUsed, reviewer_raise: null, final_decision: $finalDecision, agent_ceiling: $agentCeiling, tool_allowlist: $toolAllowlist, blocked_tools: $blockedTools, higher_authority_owner: (if $higherAuthorityOwner == "" then null else $higherAuthorityOwner end), review_log_path: $reviewLogPath, created_at: $createdAt, updated_at: $createdAt }' > "$TASK_METADATA_PATH" jq -n \ --arg title "$FIRST_DELIVERABLE" \ --arg description "First task for $ROLE_NAME. Mandate: $MANDATE. Blast-radius metadata: $TASK_METADATA_PATH" \ --arg assigneeAgentId "$HIRE_AGENT_ID" \ --arg status "todo" \ --arg priority "medium" \ --arg projectId "${PROJECT_ID:-}" \ --arg goalId "${GOAL_ID:-}" \ '{ title: $title, description: $description, assigneeAgentId: $assigneeAgentId, status: $status, priority: $priority } | if $projectId == "" then . else .projectId = $projectId end | if $goalId == "" then . else .goalId = $goalId end' \ > "$STATE_DIR/step-4/$ROLE_NAME-first-task-payload.json" curl -fsS -X POST "$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/issues" \ -H "Content-Type: application/json" \ -d @"$STATE_DIR/step-4/$ROLE_NAME-first-task-payload.json" \ -o "$STATE_DIR/step-4/$ROLE_NAME-first-task.json" PAPERCLIP_TASK_ID="$(jq -r '.id // .data.id // empty' "$STATE_DIR/step-4/$ROLE_NAME-first-task.json")" if [ -n "$PAPERCLIP_TASK_ID" ]; then tmp="$(mktemp)" jq --arg taskId "$PAPERCLIP_TASK_ID" '.task_id = $taskId' "$TASK_METADATA_PATH" > "$tmp" && mv "$tmp" "$TASK_METADATA_PATH" mkdir -p "$STATE_DIR/blast-radius/tasks" cp "$TASK_METADATA_PATH" "$STATE_DIR/blast-radius/tasks/$PAPERCLIP_TASK_ID.json" fi fi ``` Alive check: ```bash if [ -z "$HIRE_APPROVAL_ID" ]; then curl -fsS -X POST "$PAPERCLIP_URL/api/plugins/paperclip-plugin-connectors/actions/governor.status" \ -H "Content-Type: application/json" \ -d "{\"companyId\":\"$PAPERCLIP_COMPANY_ID\"}" \ -o "$STATE_DIR/step-4/$ROLE_NAME-governor.json" curl -fsS "$PAPERCLIP_URL/api/companies/$PAPERCLIP_COMPANY_ID/agents" \ -o "$STATE_DIR/step-4/$ROLE_NAME-live-agents-after.json" jq --arg id "$HIRE_AGENT_ID" '[.[] | select(.id == $id)] | length == 1' \ "$STATE_DIR/step-4/$ROLE_NAME-live-agents-after.json" \ > "$STATE_DIR/step-4/$ROLE_NAME-alive-check.json" fi ``` If the first hire cannot pick up or complete work, freeze additional hires for that C-level and report the blocker. ## Regenerate Org Chart After every hire, regenerate `_org-chart.md` from `state/agent-registry.json`. Do not edit it by hand and do not parse `AGENTS.md`. ```bash jq -r ' "# Org Chart\n", (.agents | to_entries | sort_by(.key)[] | "- \(.key) (\(.value.productRole // "agent"), \(.value.bucket // "unknown")) reports to \(.value.reportsToName // "none")") ' "$REGISTRY" > "$BASE/agents/_org-chart.md" ``` Also append a machine-readable hire log: ```bash jq -n \ --arg at "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ --arg role "$ROLE_NAME" \ --arg id "$HIRE_AGENT_ID" \ --arg reportsTo "$REPORTS_TO_ID" \ --arg approval "$HIRE_APPROVAL_ID" \ --arg path "$BASE/agents/$ROLE_NAME/AGENTS.md" \ '{hired_at: $at, role_name: $role, paperclip_agent_id: $id, reports_to: $reportsTo, approval_id: (if $approval == "" then null else $approval end), agents_md_path: $path}' \ >> "$BASE/governance/hire-log.jsonl" ``` ## Step 4 Done Set final setup state: ```bash update_setup_step "step-4.done" ``` Report each approved hire, Paperclip agent ID, reports-to UUID, `AGENTS.md` path, first task ID if created, alive-check evidence, pending approval ID if any, and any paused hires.