Agent Please turns issue tracker tasks into isolated, autonomous implementation runs — managing work instead of supervising coding agents.
Warning: Agent Please is an engineering preview for use in trusted environments.
- Overview
- Key Differences from Symphony
- Features
- Architecture
- Quick Start
- WORKFLOW.md Configuration
- CLI Usage
- GitHub App Authentication
- Slack Notifications
- Trust and Safety
- License
Agent Please is a long-running TypeScript service that:
- Polls an issue tracker (GitHub Projects v2 or Asana) for tasks in configured active states.
- Creates an isolated workspace directory for each eligible issue.
- Launches a Claude Code agent session inside that workspace with a rendered prompt.
- Monitors the session, handles retries, and reconciles issue state on each poll cycle.
It is a TypeScript implementation of the Symphony specification, adapted for GitHub Projects v2 / Asana and Claude Code instead of Linear and Codex.
For full technical details, see SPEC.md.
| Symphony (reference) | Agent Please | |
|---|---|---|
| Issue Tracker | Linear | GitHub Projects v2 & Asana (under development) |
| Coding Agent | Codex (app-server mode) | Claude Code CLI |
| Language | Elixir/OTP | TypeScript + Bun |
| Tracker Auth | LINEAR_API_KEY |
GITHUB_TOKEN, GitHub App credentials, or ASANA_ACCESS_TOKEN |
| Project Config | project_slug |
owner + project_number (GitHub Projects v2) or project_gid (Asana) |
| Issue States | Linear workflow states | GitHub Projects v2 Status field / Asana sections |
| Agent Protocol | JSON-RPC over stdio | @anthropic-ai/claude-agent-sdk |
| Permission Model | Codex approval/sandbox policies | Claude Code --permission-mode |
- Multi-tracker support — Dispatch work from GitHub Projects v2 items or Asana tasks (under development) on a fixed cadence.
- GitHub App authentication — Authenticate the GitHub tracker with a GitHub App installation
token (
app_id+private_key+installation_id) instead of a PAT, for fine-grained permissions and higher API rate limits. - Assignee & label filters — Filter eligible issues by assignee and/or label. Multiple values
within each filter use OR logic; assignee and label filters are ANDed when both are specified.
Applies at dispatch time only — already-running issues are unaffected. Configured per-tracker
in
WORKFLOW.md. - Isolated workspaces — Each issue gets a dedicated directory; workspaces persist across runs.
WORKFLOW.mdconfig — Version agent prompt and runtime settings alongside your code.- Bounded concurrency — Global and per-state concurrent agent limits.
- Retry with backoff — Exponential backoff on failures; short continuation retry on clean exit.
- Dynamic config reload — Edit
WORKFLOW.mdand changes apply without restarting the service. - Workspace hooks — Shell scripts run at
after_create,before_run,after_run, andbefore_removelifecycle events. - Structured logging — Operator-visible logs with stable
key=valueformat. - Slack notifications — @mention the bot in Slack to get orchestrator status. Uses Chat SDK Slack adapter.
- Optional HTTP dashboard — Enable with
--portfor runtime status and JSON API.
WORKFLOW.md
|
v
Config Layer ──> Orchestrator ──> Workspace Manager ──> Agent Runner (Claude Code)
| |
v v
Issue Tracker Client Isolated workspace/
(GitHub GraphQL API or per-issue directory
Asana REST API,
polling + reconciliation)
|
v
Status Surface (optional HTTP dashboard / structured logs)
Components:
- Workflow Loader — Parses
WORKFLOW.mdYAML front matter and prompt template body. - Config Layer — Typed getters with env-var indirection and built-in defaults.
- Issue Tracker Client — Fetches candidate issues, reconciles running-issue states. Supports GitHub Projects v2 (GraphQL API) and Asana (REST API) adapters.
- Orchestrator — Owns in-memory state; drives the poll/dispatch/retry loop.
- Workspace Manager — Creates, reuses, and cleans per-issue workspaces; runs hooks.
- Agent Runner — Launches Claude Code, streams events back to the orchestrator.
- Status Surface — Optional terminal view and HTTP API for operator visibility.
See SPEC.md for the full specification.
- Bun (see bun.sh for installation)
- Claude Code CLI (see the official installation guide)
- GitHub token (
GITHUB_TOKEN) with access to the target project, or GitHub App credentials (GITHUB_APP_ID,GITHUB_APP_PRIVATE_KEY,GITHUB_APP_INSTALLATION_ID) — see GitHub App Authentication, or Asana access token (ASANA_ACCESS_TOKEN) (under development)
git clone https://github.com/pleaseai/agent-please.git
cd agent-please
bun install
bun run buildCreate a WORKFLOW.md in your target repository. Two examples are shown below.
See also the example WORKFLOW.md for a real-world reference.
See also the example GitHub Project for a real-world reference.
---
platforms:
github:
api_key: $GITHUB_TOKEN
owner: your-org
bot_username: agent-please
projects:
- platform: github
project_number: 42
active_statuses:
- Todo
- In Progress
- Merging
- Rework
terminal_statuses:
- Closed
- Cancelled
- Canceled
- Duplicate
- Done
watched_statuses:
- Human Review
polling:
interval_ms: 30000
workspace:
root: ~/agent-please_workspaces
hooks:
after_create: |
git clone https://github.com/your-org/your-repo.git .
bun install
agent:
max_concurrent_agents: 3
max_turns: 20
claude:
permission_mode: acceptEdits
# setting_sources: [] # default: [project, local, user]; set [] for SDK isolation mode
turn_timeout_ms: 3600000
---
You are working on a GitHub issue for the repository `your-org/your-repo`.
Issue {{ issue.identifier }}: {{ issue.title }}
{{ issue.description }}
{% if issue.blocked_by.size > 0 %}
Blocked by:
{% for blocker in issue.blocked_by %}
- {{ blocker.identifier }} ({{ blocker.state }})
{% endfor %}
{% endif %}
{% if attempt %}
This is attempt #{{ attempt }}. Review any prior work in the workspace before continuing.
{% endif %}
Your task:
1. Understand the issue requirements.
2. Implement the requested changes.
3. Write or update tests as needed.
4. Open a pull request and move this issue to `Human Review`.Use GitHub App credentials instead of a PAT for fine-grained permissions and higher API rate limits:
---
platforms:
github:
app_id: $GITHUB_APP_ID
private_key: $GITHUB_APP_PRIVATE_KEY
installation_id: $GITHUB_APP_INSTALLATION_ID
owner: your-org
bot_username: agent-please
projects:
- platform: github
project_number: 42
active_statuses:
- Todo
- In Progress
- Merging
- Rework
terminal_statuses:
- Closed
- Cancelled
- Canceled
- Duplicate
- Done
watched_statuses:
- Human Review
polling:
interval_ms: 30000
workspace:
root: ~/agent-please_workspaces
hooks:
after_create: |
git clone https://github.com/your-org/your-repo.git .
bun install
agent:
max_concurrent_agents: 3
max_turns: 20
claude:
permission_mode: acceptEdits
# setting_sources: [] # default: [project, local, user]; set [] for SDK isolation mode
turn_timeout_ms: 3600000
---
You are working on a GitHub issue for the repository `your-org/your-repo`.
Issue {{ issue.identifier }}: {{ issue.title }}
{{ issue.description }}
{% if issue.blocked_by.size > 0 %}
Blocked by:
{% for blocker in issue.blocked_by %}
- {{ blocker.identifier }} ({{ blocker.state }})
{% endfor %}
{% endif %}
{% if attempt %}
This is attempt #{{ attempt }}. Review any prior work in the workspace before continuing.
{% endif %}
Your task:
1. Understand the issue requirements.
2. Implement the requested changes.
3. Write or update tests as needed.
4. Open a pull request and move this issue to `Human Review`.Note: Asana support is under development. The configuration below is a preview and may change.
---
platforms:
asana:
api_key: $ASANA_ACCESS_TOKEN
projects:
- platform: asana
project_gid: "1234567890123456"
active_sections:
- In Progress
terminal_sections:
- Done
- Cancelled
polling:
interval_ms: 30000
workspace:
root: ~/agent-please_workspaces
hooks:
after_create: |
git clone https://github.com/your-org/your-repo.git .
bun install
agent:
max_concurrent_agents: 3
max_turns: 20
claude:
permission_mode: acceptEdits
# setting_sources: [] # default: [project, local, user]; set [] for SDK isolation mode
turn_timeout_ms: 3600000
---
You are working on an Asana task for the project.
Task: {{ issue.title }}
{{ issue.description }}
{% if issue.blocked_by.size > 0 %}
Blocked by:
{% for blocker in issue.blocked_by %}
- {{ blocker.identifier }} ({{ blocker.state }})
{% endfor %}
{% endif %}
{% if attempt %}
This is attempt #{{ attempt }}. Review any prior work in the workspace before continuing.
{% endif %}
Your task:
1. Understand the task requirements.
2. Implement the requested changes.
3. Write or update tests as needed.
4. Open a pull request and move this task to the review section (e.g. `Human Review`).# Set your tracker token (GitHub PAT)
export GITHUB_TOKEN=ghp_your_token_here
# or (GitHub App)
export GITHUB_APP_ID=12345
export GITHUB_APP_PRIVATE_KEY="$(cat path/to/private-key.pem)"
export GITHUB_APP_INSTALLATION_ID=67890
# or (Asana — under development)
export ASANA_ACCESS_TOKEN=your_token_here
# Run Agent Please against a WORKFLOW.md in the current directory
bunx agent-please
# Or specify a WORKFLOW.md path
bunx agent-please /path/to/WORKFLOW.md
# Enable the optional HTTP dashboard on port 3000
bunx agent-please --port 3000WORKFLOW.md is the single source of truth for Agent Please's runtime behavior. It combines a YAML
front matter configuration block with a Markdown prompt template body.
---
platforms:
github: # Platform name used by projects[].platform and channels[].platform
api_key: $GITHUB_TOKEN # Required: token or $ENV_VAR (or use GitHub App fields below)
owner: your-org # Required unless projects[].project_id is set
bot_username: agent-please # Optional: bot display name for chat channels
endpoint: https://api.github.com # Optional: override GitHub API base URL
# GitHub App authentication (alternative to api_key — all three required together):
# app_id: $GITHUB_APP_ID # Optional: GitHub App ID (integer or $ENV_VAR)
# private_key: $GITHUB_APP_PRIVATE_KEY # Optional: GitHub App private key PEM or $ENV_VAR
# installation_id: $GITHUB_APP_INSTALLATION_ID # Optional: installation ID (integer or $ENV_VAR)
# asana: # UNDER DEVELOPMENT
# api_key: $ASANA_ACCESS_TOKEN # Required: token or $ENV_VAR
# endpoint: https://app.asana.com/api/1.0 # Optional: override Asana API base URL
# slack:
# bot_token: $SLACK_BOT_TOKEN
# signing_secret: $SLACK_SIGNING_SECRET
projects:
- platform: github # Required: must match a key in platforms
project_number: 42 # Required: GitHub Projects v2 project number
project_id: PVT_kwDOxxxxx # Optional: project node ID (bypasses owner+project_number lookup)
active_statuses: # Optional: default ["Todo", "In Progress", "Merging", "Rework"]
- Todo
- In Progress
- Merging
- Rework
terminal_statuses: # Optional: default ["Closed", "Cancelled", "Canceled", "Duplicate", "Done"]
- Closed
- Cancelled
- Canceled
- Duplicate
- Done
watched_statuses: # Optional: states polled for dispatch on review activity. Default ["Human Review"]
- Human Review
# filter:
# assignee: user1, user2 # Optional: CSV or YAML array; case-insensitive OR match
# # (unassigned issues are excluded when this filter is set)
# label: bug, feature # Optional: CSV or YAML array; case-insensitive OR match
# Both filters AND together when both are specified. Applies at dispatch time only.
# - platform: asana # UNDER DEVELOPMENT
# project_gid: "1234567890123456" # Required: Asana project GID
# active_sections: # Optional: default ["To Do", "In Progress"]
# - In Progress
# terminal_sections: # Optional: default ["Done", "Cancelled"]
# - Done
# - Cancelled
channels:
- platform: github # Required: must match a key in platforms
allowed_associations: # Optional: GitHub comment author associations permitted to trigger dispatch
- OWNER
- MEMBER
- COLLABORATOR
# - platform: slack
polling:
interval_ms: 30000 # Optional: poll cadence in ms, default 30000
workspace:
root: ~/agent-please_workspaces # Optional: default <tmpdir>/agent-please_workspaces
hooks:
after_create: | # Optional: run once when workspace is first created
git clone https://github.com/your-org/your-repo.git .
before_run: | # Optional: run before each agent attempt
git pull --rebase
after_run: | # Optional: run after each agent attempt
echo "Run completed"
before_remove: | # Optional: run before workspace deletion
echo "Cleaning up"
timeout_ms: 60000 # Optional: hook timeout in ms, default 60000
agent:
max_concurrent_agents: 10 # Optional: global concurrency limit, default 10
max_turns: 20 # Optional: max turns per agent run, default 20
max_retry_backoff_ms: 300000 # Optional: max retry delay in ms, default 300000
max_concurrent_agents_by_state: # Optional: per-state concurrency limits
in progress: 5
claude:
command: claude # Optional: Claude Code CLI command, default "claude"
model: claude-sonnet-4-5-20250514 # Optional: override Claude model. Default: CLI default
effort: high # Optional: reasoning depth — 'low', 'medium', 'high', or 'max'. Default 'high'.
permission_mode: acceptEdits # Optional: one of 'default', 'acceptEdits', 'bypassPermissions'. Defaults to 'bypassPermissions'.
allowed_tools: # Optional: restrict available tools
- Read
- Write
- Bash
setting_sources: # Optional: filesystem settings to load. Default: [project, local, user]
- project # load .claude/settings.json + CLAUDE.md from the workspace directory
- local # load .claude/settings.local.json from the workspace directory
- user # load ~/.claude/settings.json + global CLAUDE.md
# Only "project", "local", and "user" are valid — other values are ignored
turn_timeout_ms: 3600000 # Optional: per-turn timeout in ms, default 3600000
read_timeout_ms: 5000 # Optional: initial subprocess read timeout in ms, default 5000
stall_timeout_ms: 300000 # Optional: stall detection timeout, default 300000
system_prompt: "custom prompt" # Optional: custom system prompt string. Default: built-in claude_code preset
settings:
attribution:
commit: "🙏 Generated with [Agent Please](https://github.com/pleaseai/agent-please)" # Optional: appended to git commit messages. Defaults to Agent Please link.
pr: "🙏 Generated with [Agent Please](https://github.com/pleaseai/agent-please)" # Optional: appended to PR descriptions. Defaults to Agent Please link.
# worker: # Optional: SSH worker support (experimental)
# ssh_hosts: # List of SSH host aliases for remote execution
# - worker-1
# - worker-2
# max_concurrent_agents_per_host: 5 # Max agents per SSH host
# observability: # Optional: TUI dashboard settings
# dashboard_enabled: true # Enable TUI dashboard, default true
# refresh_ms: 1000 # Dashboard data refresh interval, default 1000
# render_interval_ms: 16 # TUI render interval, default 16
server:
port: 3000 # Optional: enable HTTP dashboard on this port
host: "127.0.0.1" # Optional: bind address, default "127.0.0.1"
# Webhook endpoints:
# GitHub: POST /api/webhooks/github (requires server.webhook.secret)
# Slack: POST /api/webhooks/slack (requires SLACK_BOT_TOKEN + SLACK_SIGNING_SECRET env vars)
---
Your prompt template goes here. Available variables:
- {{ issue.id }} — Tracker-internal issue ID
- {{ issue.identifier }} — Human-readable identifier (e.g. "#42" or task GID)
- {{ issue.title }} — Issue title
- {{ issue.description }} — Issue body/description
- {{ issue.state }} — Current tracker state name
- {{ issue.url }} — Issue URL
- {{ issue.assignees }} — Array of assignee logins (GitHub) or emails (Asana)
- {{ issue.labels }} — Array of label strings (normalized to lowercase)
- {{ issue.blocked_by }} — Array of blocker refs (each has id, identifier, state)
- {{ issue.branch_name }} — PR head branch name (for PullRequest items) or null
- {{ issue.pull_requests }} — Array of linked PRs (each has number, title, url, state, branch_name)
- {{ issue.review_decision }} — PR review decision: "approved", "changes_requested", "commented", "review_required", or null
- {{ issue.priority }} — Numeric priority or null
- {{ issue.created_at }} — ISO-8601 creation timestamp
- {{ issue.updated_at }} — ISO-8601 last-updated timestamp
- {{ issue.project }} — GitHub Projects v2 context (null for Asana):
- {{ issue.project.owner }} — Project owner login
- {{ issue.project.number }} — Project number
- {{ issue.project.project_id }} — Project GraphQL node ID (resolved at runtime)
- {{ issue.project.item_id }} — Project item GraphQL node ID
- {{ issue.project.field_id }} — Status field GraphQL node ID (resolved at runtime)
- {{ issue.project.status_options }} — Array of { name, id } for status field options
- {{ attempt }} — Retry attempt number (null on first run)The prompt template uses Liquid-compatible syntax. All issue fields are available:
{{ issue.identifier }}: {{ issue.title }}
{{ issue.description }}
State: {{ issue.state }}
{% if issue.blocked_by.size > 0 %}
Blocked by:
{% for blocker in issue.blocked_by %}
- {{ blocker.identifier }} ({{ blocker.state }})
{% endfor %}
{% endif %}
{% if issue.pull_requests.size > 0 %}
Linked pull requests:
{% for pr in issue.pull_requests %}
- PR #{{ pr.number }}: {{ pr.title }} ({{ pr.state }}){% if pr.branch_name %} — branch: {{ pr.branch_name }}{% endif %}{% if pr.url %} — {{ pr.url }}{% endif %}
{% endfor %}
{% endif %}
{% if attempt %}
Retry attempt: {{ attempt }}
{% endif %}# Basic usage (reads WORKFLOW.md from current directory)
agent-please
# Specify WORKFLOW.md path (positional argument)
agent-please ./WORKFLOW.md
# Enable HTTP dashboard
agent-please --port 3000
# Initialize a new GitHub Projects v2 project and scaffold WORKFLOW.md
# (Requires GITHUB_TOKEN environment variable to be set)
agent-please init --owner <org-or-user> --title "My Project"
# Alternatively, provide the token via a flag:
agent-please init --owner <org-or-user> --title "My Project" --token <your-github-token>
# Show help
agent-please --helpThe github_projects tracker supports two authentication methods:
| Method | Config fields | When to use |
|---|---|---|
| PAT | api_key |
Personal access tokens — quick setup |
| GitHub App | app_id, private_key, installation_id |
Organizations — fine-grained permissions, higher rate limits |
When both are present, api_key (PAT) takes precedence.
- Create a GitHub App with the following permissions:
- Repository permissions:
Contents: Read-onlyIssues: Read & writePull requests: Read & write
- Organization permissions:
Projects: Read & write
- Repository permissions:
- Install the app on your organization and note the installation ID (visible in the app's installation settings URL).
- Generate a private key (
.pemfile) from the app's settings page. - Set the environment variables:
export GITHUB_APP_ID=12345
export GITHUB_APP_PRIVATE_KEY="$(cat /path/to/private-key.pem)"
export GITHUB_APP_INSTALLATION_ID=67890- Reference them in
WORKFLOW.md:
platforms:
github:
app_id: $GITHUB_APP_ID
private_key: $GITHUB_APP_PRIVATE_KEY
installation_id: $GITHUB_APP_INSTALLATION_ID
owner: your-org
projects:
- platform: github
project_number: 42The values can also be inlined directly (not recommended for secrets):
app_id: 12345
private_key: "-----BEGIN RSA PRIVATE KEY-----\n..."
installation_id: 67890Agent Please validates GitHub App config at startup:
| Scenario | Result |
|---|---|
api_key set |
PAT auth — app fields ignored |
All three app fields set (app_id, private_key, installation_id) |
App auth |
| Only some app fields set | incomplete_github_app_config error |
| No auth configured | missing_tracker_api_key error |
Agent Please supports Slack as a notification channel via the Chat SDK Slack adapter. When configured, you can @mention the bot in any Slack channel to get real-time orchestrator status (running issues, retry queue, token usage).
SLACK_BOT_TOKEN=xoxb-... # Bot User OAuth Token
SLACK_SIGNING_SECRET=... # Signing secret for webhook verificationWhen both environment variables are set, the Slack adapter is automatically enabled alongside any configured tracker (GitHub Projects v2 or Asana).
Point your Slack app's Event Subscriptions and Interactivity request URL to:
https://your-domain.com/api/webhooks/slack
- Go to api.slack.com/apps and click Create New App > From an app manifest.
- Select your workspace and paste the following manifest:
display_information:
name: Agent Please
description: Orchestrator status bot
features:
bot_user:
display_name: Agent Please
always_online: true
oauth_config:
scopes:
bot:
- app_mentions:read
- channels:history
- channels:read
- chat:write
- groups:history
- groups:read
- im:history
- im:read
- reactions:read
- reactions:write
- users:read
settings:
event_subscriptions:
request_url: https://your-domain.com/api/webhooks/slack
bot_events:
- app_mention
- message.channels
- message.groups
- message.im
interactivity:
is_enabled: true
request_url: https://your-domain.com/api/webhooks/slack
org_deploy_enabled: false
socket_mode_enabled: false
token_rotation_enabled: false- Replace
https://your-domain.com/api/webhooks/slackwith your deployed webhook URL. - Click Create, then go to Basic Information > App Credentials and copy the Signing Secret as
SLACK_SIGNING_SECRET. - Go to OAuth & Permissions, click Install to Workspace, and copy the Bot User OAuth Token (
xoxb-...) asSLACK_BOT_TOKEN. - Invite the bot to a channel and @mention it to see the orchestrator status.
Agent Please runs Claude Code autonomously. Understand the trust implications before deploying.
| Mode | Behavior | Recommended For |
|---|---|---|
default |
Interactive approval for sensitive operations | Development, unknown repositories |
acceptEdits |
Auto-approve file edits; prompt for shell commands | Trusted codebases |
bypassPermissions |
Auto-approve all operations | Sandboxed CI environments |
Start with default or acceptEdits unless you are running in a fully isolated environment.
- Each issue runs in a dedicated directory under
workspace.root. - Claude Code's working directory is validated against the workspace path before launch.
- Workspace paths are sanitized to prevent path traversal attacks.
- Use
acceptEditspermission mode as a baseline for most deployments. - Use
bypassPermissionsonly in network-isolated CI runners or Docker containers. - Set
agent.max_concurrent_agentsconservatively when first testing. - Monitor agent runs via the HTTP dashboard (
--port) or structured logs. - Keep API tokens scoped to the minimum required permissions.
Functional Source License 1.1, MIT Future License (FSL-1.1-MIT). See LICENSE for details.
- Agent Please is a TypeScript implementation based on the Symphony specification by OpenAI (Apache 2.0).
- This project uses the Claude Agent SDK, which is subject to Anthropic's Commercial Terms of Service.