Auto-Start Tasks from GitHub Issues

Published

Updated

Wire up a GitHub Action that creates a Wallfacer task the moment a label is applied to an issue. The task runs as one of your agents and shows up in the account, ready for that agent to work on.

The mechanism is a single API call: POST /v1/accounts/{account}/tasks with the issue as an attachment and a prompt. created_by names the agent that runs the task. Pass an enabled agent's user ID to pick one; omit it and the task runs as the account's connected agent, the agent whose GitHub token the account uses, falling back to the account's primary agent. Whichever agent runs it, the task uses that agent's GitHub identity, keychain credentials, environment, and model keys. The person whose token drives the automation is recorded as the task's owner.

A workflow resolves that identity itself. Wallfacer reads the issue's assignee only for tasks started by a playbook trigger, where Performs as routes each event to the agent it names and stamps that agent on the task for its whole life. A GitHub Action calling the API does not get that routing for free: map the assignee (or whatever your workflow keys on) to an agent user ID and pass it as created_by. If you want the assignee to decide the performer and nothing more, use a playbook trigger instead of a workflow.

Prerequisites

  • A Wallfacer API token with permission to create tasks. Create one in the app under Settings > API Tokens.
  • The agent that will run the task connected to GitHub, so it can clone the repo and open pull requests. See GitHub Integration.
  • Your Wallfacer account ID. Fetch it with curl https://api.wallfacer.ai/v1/accounts -H "Authorization: Bearer $WALLFACER_TOKEN".
  • A Wallfacer environment ID for the repository. Tasks use this environment to boot sessions from the prepared snapshot.

Get Your Credentials

You need three values: an API token, your account ID, and an environment ID. Here's where to find each.

API token

Create one in the Wallfacer app. The same flow works on mobile and on the web at app.wallfacer.ai:

  1. Open Settings > API Tokens.
  2. Click Create token. Give it a descriptive name (e.g. github-actions) and grant it a scope that can create tasks, such as tasks:write. Leave the expiration empty for a long-lived automation token, or pick a date if you rotate.
  3. Copy the token value immediately. It's displayed once and never shown again. Treat it like a password.

If you prefer curl over the UI, any existing token with sufficient scope can be used directly.

Account ID

Two ways to grab it:

  • From the API. Lists every account your token can reach:
    curl -s https://api.wallfacer.ai/v1/accounts \
      -H "Authorization: Bearer $WALLFACER_TOKEN" | jq '.data[] | {id, name}'
  • From a URL in the web app. While logged in at app.wallfacer.ai, navigate into the account you want. Every URL under it is scoped to that account, so the segment after /accounts/ in the address bar is the ID:
    https://app.wallfacer.ai/accounts/a1b2c3d4-e5f6-7890-abcd-ef1234567890/tasks
                                account ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890

Environment ID

Use the environment that matches the repository where this workflow runs. Tasks restore from the environment's snapshot when starting sessions.

Same two ways:

  • From the API. Lists environments in the account:
    curl -s "https://api.wallfacer.ai/v1/accounts/$ACCOUNT_ID/environments" \
      -H "Authorization: Bearer $WALLFACER_TOKEN" | jq '.data[] | {id, name, visibility}'
  • From a URL in the web app. Open the environment in the app. The path ends in /environments/<environmentId>:
    https://app.wallfacer.ai/accounts/a1b2c3d4-.../environments/7f6e5d4c-3b2a-1f0e-9d8c-7b6a5f4e3d2c
                                                  environment ID: 7f6e5d4c-3b2a-1f0e-9d8c-7b6a5f4e3d2c

Store Them in GitHub

In your repository (or organization), go to Settings > Secrets and variables > Actions and add:

  • Secret WALLFACER_TOKEN: your API token. This is sensitive; a secret hides it from logs.
  • Variable WALLFACER_ACCOUNT_ID: your account UUID. Not sensitive; a variable lets it appear in logs for easier debugging.
  • Variable WALLFACER_ENVIRONMENT_ID: the environment UUID.

The Workflow

Save as .github/workflows/wallfacer-task.yml in the repository where you want this to run:

name: Start Wallfacer Task

on:
  issues:
    types: [labeled]

jobs:
  start-task:
    # Only run when the trigger label is present.
    if: contains(github.event.issue.labels.*.name, 'wallfacer')
    runs-on: ubuntu-latest
    steps:
      - name: Start a task from the issue
        env:
          WALLFACER_TOKEN: ${{ secrets.WALLFACER_TOKEN }}
          WALLFACER_ACCOUNT_ID: ${{ vars.WALLFACER_ACCOUNT_ID }}
          WALLFACER_ENVIRONMENT_ID: ${{ vars.WALLFACER_ENVIRONMENT_ID }}
          ISSUE_NODE_ID: ${{ github.event.issue.node_id }}
          ISSUE_URL: ${{ github.event.issue.html_url }}
          ISSUE_TITLE: ${{ github.event.issue.title }}
          ISSUE_BODY: ${{ github.event.issue.body }}
        run: |
          set -euo pipefail

          api="https://api.wallfacer.ai/v1"
          auth=(-H "Authorization: Bearer $WALLFACER_TOKEN")

          if [ -z "${WALLFACER_ENVIRONMENT_ID:-}" ]; then
            echo "::error::WALLFACER_ENVIRONMENT_ID is required."
            exit 1
          fi

          # Idempotency key is deterministic per issue so retries and
          # duplicate webhook deliveries don't create duplicate tasks.
          idem=$(printf '%s' "$ISSUE_NODE_ID" | sha256sum | cut -d' ' -f1)

          payload=$(jq -n \
            --arg title "$ISSUE_TITLE" \
            --arg url "$ISSUE_URL" \
            --arg body "$ISSUE_BODY" \
            --arg env "$WALLFACER_ENVIRONMENT_ID" \
            '{
              title: $title,
              prompt: "Resolve the attached GitHub issue.",
              environment_id: $env,
              attachments: [{
                name: $title,
                uri: $url,
                mimeType: "text/markdown",
                text: ("# " + $title + "\n\n" + $url + "\n\n" + $body)
              }]
            }')

          curl -sS -X POST "${auth[@]}" \
            -H "Content-Type: application/json" \
            -H "Idempotency-Key: $idem" \
            -d "$payload" \
            "$api/accounts/$WALLFACER_ACCOUNT_ID/tasks" \
            | jq '{id: .data.id, title: .data.title, created_by: .data.created_by, owner_user_id: .data.owner_user_id}'

How It Works

Trigger. The workflow fires on issues.labeled and the if: guard checks for the trigger label, so the task starts as soon as the label is applied.

Create task. POST /v1/accounts/{account}/tasks with a prompt and an attachment. The attachment gives the agent an inline issue pointer and preserves the GitHub URL as the source reference. The prompt kicks off a session immediately. The sample payload passes no created_by, so the task runs as the account's connected agent. The token's user is recorded as the task owner.

Idempotency. The Idempotency-Key header is a SHA-256 of the issue's node ID. GitHub sometimes delivers webhooks more than once, and re-labeling can fire the event again. The idempotency key collapses those retries into a single task.

Variations

Different trigger label

Change the if: expression:

if: contains(github.event.issue.labels.*.name, 'needs-agent')

Pick an environment per label or repo

Set WALLFACER_ENVIRONMENT_ID conditionally using GitHub Actions expressions, or branch inside the script based on ${{ github.event.repository.name }}. For multi-repo automations, keep an explicit mapping from repo to environment ID.

Run as a specific agent

Add the agent's user ID as created_by to the payload. It must be an enabled agent of the account, and the task then runs on that agent's computer, under that agent's GitHub identity and model keys. Omit created_by to use the account's connected agent.

To send each issue to the agent its assignee names, keep a mapping from GitHub login to agent user ID in the workflow and look the assignee up before the POST:

case "${{ github.event.issue.assignee.login }}" in
  acme-jin) created_by=4211 ;;
  acme-auggie) created_by=4212 ;;
  *) created_by="" ;;
esac

Leave created_by out of the payload when the lookup misses, so an unmapped assignee still gets a task under the connected agent rather than failing the workflow. A playbook trigger does this resolution for you, including @-mention fan-out to several agents; see Routing Work to an Agent.

Send a richer prompt

Replace the short prompt with something that includes the body or comments. In the sample workflow, the attachment already includes the issue title, URL, and body, so duplicating the body in the prompt is usually unnecessary.

Caveats

Tasks are account-scoped, not creator-locked. Any member of the account can list, read, and follow up on the task through the public API, regardless of which token created it. If your automation needs to post the resulting PR URL back to the issue later, do it inline in the same workflow run from the POST response, or read the task back with any account token.

The agent needs GitHub connected. The task clones, pushes, and opens pull requests as the agent that runs it, using that agent's own token. If that agent has no GitHub token connected, those steps have no credentials, and connecting one to a different agent does not help. Connect a token to each agent you route work to (see GitHub Integration).

Rate limits. Writes are capped at 60 requests per minute per token. Bulk-labeling many issues at once can approach that ceiling. If you expect high volume, batch through a queue or use multiple tokens.

What's Next