From 81bfb8085a6eff77e9a6d1394818f3f7b9cc7ba7 Mon Sep 17 00:00:00 2001 From: openhands Date: Wed, 25 Mar 2026 14:31:35 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20feat:=20rent-a-human=20=E2=80=94=20f?= =?UTF-8?q?ormula-dispatchable=20human=20action=20drafts=20(#679)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add run-rent-a-human formula that lets any agent dispatch a human action (post on Reddit, comment on HN, sign up for a service, etc.). Claude drafts copy-paste-ready content to vault/outreach/{platform}/drafts/ and notifies the human via Matrix for one-click execution. Co-Authored-By: Claude Opus 4.6 (1M context) --- formulas/run-rent-a-human.toml | 181 +++++++++++++++++++++++++++++++++ vault/outreach/.gitkeep | 0 2 files changed, 181 insertions(+) create mode 100644 formulas/run-rent-a-human.toml create mode 100644 vault/outreach/.gitkeep diff --git a/formulas/run-rent-a-human.toml b/formulas/run-rent-a-human.toml new file mode 100644 index 0000000..c50e181 --- /dev/null +++ b/formulas/run-rent-a-human.toml @@ -0,0 +1,181 @@ +# formulas/run-rent-a-human.toml — Draft human actions for one-click execution +# +# "Rent a Human" — when the factory needs a human to do something it can't +# (post on Reddit, sign up for a service, approve a payment, etc.), it drafts +# the action and notifies the human for one-click copy-paste execution. +# +# Trigger: action issue created by planner or any formula. +# The action-agent picks up the issue, executes these steps, writes a draft +# to vault/outreach/{platform}/drafts/, notifies the human via Matrix, +# and closes the issue. +# +# YAML front matter in the dispatching action issue: +# formula: run-rent-a-human +# vars: +# platform: reddit +# action_type: post +# context: "Write about our AI agent factory for r/autonomousAI" + +name = "run-rent-a-human" +description = "Draft a human action (post, comment, signup, etc.) for one-click execution" +version = 1 + +[vars.platform] +description = "Target platform (reddit, hackernews, twitter, linkedin, etc.)" +required = true + +[vars.action_type] +description = "Type of action (post, comment, signup, reply, payment, etc.)" +required = true + +[vars.context] +description = "What to write about — links, data, talking points, target subreddit/channel" +required = true + +[vars.target] +description = "Specific target (subreddit, HN category, Twitter handle, etc.)" +required = false +default = "" + +[[steps]] +id = "draft-content" +title = "Draft copy-paste-ready content for the human" +description = """ +Draft the content that a human will copy-paste onto {{platform}}. + +1. Read the context provided: {{context}} + +2. Determine the target destination: + - If {{target}} is provided, use it (e.g. r/autonomousAI, HN Show) + - If not, infer the best target from the context and platform + +3. Draft the content based on platform={{platform}} and action_type={{action_type}}: + + For reddit posts: + - Title: attention-grabbing but not clickbait, matches subreddit tone + - Body: conversational, authentic, includes relevant details + - No marketing-speak — write like a developer sharing their project + + For hackernews posts: + - Title: factual, follows HN title conventions (Show HN: X — Y) + - Body/URL: concise description or link + + For twitter/X: + - Tweet text: under 280 chars, engaging, no hashtag spam + - Thread format if content needs multiple tweets + + For comments/replies: + - Natural, adds value to the conversation + - References the parent post/comment context + + For signups/registrations: + - Step-by-step instructions for the human + - What information to enter, what options to select + + For other platforms/actions: + - Adapt format to the platform conventions + - Always produce copy-paste-ready output + +4. The draft MUST be ready to use with zero editing — the human should only + need to copy-paste. No placeholders like [INSERT X HERE]. + +5. Store the drafted content in environment variables for the next step: + DRAFT_TARGET="" + DRAFT_TITLE="" + DRAFT_BODY="<body content>" + SLUG="<url-safe slug derived from title, e.g. ai-agent-factory>" + + Write these to a temp file for the next step: + cat > /tmp/rent-a-human-draft.env << 'DRAFTEOF' + DRAFT_TARGET=<value> + DRAFT_TITLE=<value> + DRAFT_BODY=<value> + SLUG=<value> + DRAFTEOF +""" + +[[steps]] +id = "write-draft" +title = "Write draft file to vault/outreach" +needs = ["draft-content"] +description = """ +Write the drafted content to the outreach directory. + +1. Read the draft from the temp file: + source /tmp/rent-a-human-draft.env + +2. Create the output directory: + DRAFT_DIR="${PROJECT_REPO_ROOT}/vault/outreach/{{platform}}/drafts" + mkdir -p "$DRAFT_DIR" + +3. Generate the filename: + DATE=$(date -u +%Y-%m-%d) + DRAFT_FILE="${DRAFT_DIR}/${DATE}-${SLUG}.md" + +4. Write the draft file in the standard format: + + # ${DRAFT_TARGET} + <!-- platform: {{platform}} --> + <!-- action: {{action_type}} --> + <!-- status: DRAFT --> + <!-- drafted: ${DATE} --> + + ## Title + ${DRAFT_TITLE} + + ## Body + ${DRAFT_BODY} + + The file must contain ONLY copy-paste-ready content under the Title and + Body headings. No instructions, no meta-commentary inside those sections. + +5. Verify the file was written: + cat "$DRAFT_FILE" + +6. Stage and commit the draft: + cd "$PROJECT_REPO_ROOT" + git add "$DRAFT_FILE" + +7. Save the file path and title for the notification step: + echo "$DRAFT_FILE" > /tmp/rent-a-human-path + echo "$DRAFT_TITLE" > /tmp/rent-a-human-title + +8. Clean up the temp env file: + rm -f /tmp/rent-a-human-draft.env +""" + +[[steps]] +id = "notify-human" +title = "Notify human via Matrix" +needs = ["write-draft"] +description = """ +Send a Matrix notification so the human knows a draft is ready. + +1. Read saved state: + DRAFT_FILE=$(cat /tmp/rent-a-human-path) + DRAFT_TITLE=$(cat /tmp/rent-a-human-title) + +2. Send the notification: + source "$FACTORY_ROOT/lib/env.sh" + matrix_send "rent-a-human" "New {{platform}} {{action_type}} draft ready: ${DRAFT_TITLE}" + +3. Post a summary comment on the action issue: + curl -sf -X POST \ + -H "Authorization: token ${FORGE_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${FORGE_API}/issues/${ISSUE}/comments" \ + -d "{\"body\": \"Draft written to \`${DRAFT_FILE}\`\\n\\nHuman: copy-paste the content, then change \`status: DRAFT\` to \`status: POSTED\` in the file.\"}" + +4. Clean up temp files: + rm -f /tmp/rent-a-human-path /tmp/rent-a-human-title + +5. Close the action issue: + curl -sf -X PATCH \ + -H "Authorization: token ${FORGE_TOKEN}" \ + -H 'Content-Type: application/json' \ + "${FORGE_API}/issues/${ISSUE}" \ + -d '{"state": "closed"}' + +6. Signal completion: + echo 'PHASE:done' > "$PHASE_FILE" +""" diff --git a/vault/outreach/.gitkeep b/vault/outreach/.gitkeep new file mode 100644 index 0000000..e69de29 From 369e19bbe2f25cdad6a201603a8a5a36298d979a Mon Sep 17 00:00:00 2001 From: openhands <openhands@all-hands.dev> Date: Wed, 25 Mar 2026 14:40:28 +0000 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20JSON?= =?UTF-8?q?=20handoff=20for=20multi-line=20bodies,=20commit+push=20draft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace env-file inter-step handoff with JSON (jq) to safely handle multi-line body content across steps - Add branch creation, git commit, and git push in write-draft step (AD-003: worktree destroyed after completion — unpushed work is lost) - Create PR in notify-human step and signal PHASE:awaiting_ci instead of PHASE:done so orchestrator manages the PR lifecycle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --- formulas/run-rent-a-human.toml | 80 +++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 34 deletions(-) diff --git a/formulas/run-rent-a-human.toml b/formulas/run-rent-a-human.toml index c50e181..b90d47d 100644 --- a/formulas/run-rent-a-human.toml +++ b/formulas/run-rent-a-human.toml @@ -79,19 +79,14 @@ Draft the content that a human will copy-paste onto {{platform}}. 4. The draft MUST be ready to use with zero editing — the human should only need to copy-paste. No placeholders like [INSERT X HERE]. -5. Store the drafted content in environment variables for the next step: - DRAFT_TARGET="<target destination, e.g. r/autonomousAI>" - DRAFT_TITLE="<title if applicable>" - DRAFT_BODY="<body content>" - SLUG="<url-safe slug derived from title, e.g. ai-agent-factory>" - - Write these to a temp file for the next step: - cat > /tmp/rent-a-human-draft.env << 'DRAFTEOF' - DRAFT_TARGET=<value> - DRAFT_TITLE=<value> - DRAFT_BODY=<value> - SLUG=<value> - DRAFTEOF +5. Store the drafted content as JSON (handles multi-line bodies safely): + jq -n \ + --arg target "<target destination, e.g. r/autonomousAI>" \ + --arg title "<title if applicable>" \ + --arg body "<body content — may be multi-line>" \ + --arg slug "<url-safe slug derived from title, e.g. ai-agent-factory>" \ + '{target: $target, title: $title, body: $body, slug: $slug}' \ + > /tmp/rent-a-human-draft.json """ [[steps]] @@ -99,10 +94,13 @@ id = "write-draft" title = "Write draft file to vault/outreach" needs = ["draft-content"] description = """ -Write the drafted content to the outreach directory. +Write the drafted content to the outreach directory, commit, and push. -1. Read the draft from the temp file: - source /tmp/rent-a-human-draft.env +1. Read the draft from the JSON temp file: + DRAFT_TARGET=$(jq -r '.target' /tmp/rent-a-human-draft.json) + DRAFT_TITLE=$(jq -r '.title' /tmp/rent-a-human-draft.json) + DRAFT_BODY=$(jq -r '.body' /tmp/rent-a-human-draft.json) + SLUG=$(jq -r '.slug' /tmp/rent-a-human-draft.json) 2. Create the output directory: DRAFT_DIR="${PROJECT_REPO_ROOT}/vault/outreach/{{platform}}/drafts" @@ -132,16 +130,22 @@ Write the drafted content to the outreach directory. 5. Verify the file was written: cat "$DRAFT_FILE" -6. Stage and commit the draft: +6. Create a branch, commit, and push the draft (AD-003: worktree is + destroyed after completion — unpushed work is lost): cd "$PROJECT_REPO_ROOT" + BRANCH="chore/rent-a-human-$(date -u +%Y%m%d-%H%M)" + git checkout -B "$BRANCH" git add "$DRAFT_FILE" + git commit -m "chore: rent-a-human draft — {{platform}} {{action_type}}" + git push -u "${FORGE_REMOTE:-origin}" "$BRANCH" -7. Save the file path and title for the notification step: +7. Save the file path, title, and branch for the notification step: echo "$DRAFT_FILE" > /tmp/rent-a-human-path echo "$DRAFT_TITLE" > /tmp/rent-a-human-title + echo "$BRANCH" > /tmp/rent-a-human-branch -8. Clean up the temp env file: - rm -f /tmp/rent-a-human-draft.env +8. Clean up the temp JSON file: + rm -f /tmp/rent-a-human-draft.json """ [[steps]] @@ -149,33 +153,41 @@ id = "notify-human" title = "Notify human via Matrix" needs = ["write-draft"] description = """ -Send a Matrix notification so the human knows a draft is ready. +Notify the human, create a PR, and hand off to the orchestrator for CI. 1. Read saved state: DRAFT_FILE=$(cat /tmp/rent-a-human-path) DRAFT_TITLE=$(cat /tmp/rent-a-human-title) + BRANCH=$(cat /tmp/rent-a-human-branch) -2. Send the notification: +2. Send the Matrix notification: source "$FACTORY_ROOT/lib/env.sh" matrix_send "rent-a-human" "New {{platform}} {{action_type}} draft ready: ${DRAFT_TITLE}" -3. Post a summary comment on the action issue: +3. Create a PR for the draft: + PR_RESPONSE=$(curl -sf -X POST \ + -H "Authorization: token ${FORGE_TOKEN}" \ + -H "Content-Type: application/json" \ + "${FORGE_API}/pulls" \ + -d "{\"title\":\"chore: rent-a-human — {{platform}} {{action_type}} draft\", + \"head\":\"${BRANCH}\",\"base\":\"${PRIMARY_BRANCH}\", + \"body\":\"Rent-a-Human draft for {{platform}} ({{action_type}}).\\n\\nDraft file: \`${DRAFT_FILE}\`\\n\\nHuman: after merge, copy-paste the content from the draft file, then change \`status: DRAFT\` to \`status: POSTED\`.\"}") + PR_NUMBER=$(echo "$PR_RESPONSE" | jq -r '.number') + +4. Post a summary comment on the action issue: curl -sf -X POST \ -H "Authorization: token ${FORGE_TOKEN}" \ -H 'Content-Type: application/json' \ "${FORGE_API}/issues/${ISSUE}/comments" \ - -d "{\"body\": \"Draft written to \`${DRAFT_FILE}\`\\n\\nHuman: copy-paste the content, then change \`status: DRAFT\` to \`status: POSTED\` in the file.\"}" + -d "{\"body\": \"Draft written to \`${DRAFT_FILE}\` — PR #${PR_NUMBER}\\n\\nHuman: after merge, copy-paste the content, then change \`status: DRAFT\` to \`status: POSTED\`.\"}" -4. Clean up temp files: - rm -f /tmp/rent-a-human-path /tmp/rent-a-human-title +5. Clean up temp files: + rm -f /tmp/rent-a-human-path /tmp/rent-a-human-title /tmp/rent-a-human-branch -5. Close the action issue: - curl -sf -X PATCH \ - -H "Authorization: token ${FORGE_TOKEN}" \ - -H 'Content-Type: application/json' \ - "${FORGE_API}/issues/${ISSUE}" \ - -d '{"state": "closed"}' +6. Signal the orchestrator to monitor CI (do NOT write PHASE:done — the + orchestrator handles PR lifecycle, CI monitoring, and review injection): + echo "PHASE:awaiting_ci" > "$PHASE_FILE" -6. Signal completion: - echo 'PHASE:done' > "$PHASE_FILE" +7. STOP and WAIT. The orchestrator polls CI, injects results and review + feedback. When you receive injected feedback, follow its instructions. """