In this tutorial, we will wire Sortie to GitHub Issues and the Copilot CLI, clone a repository, let the agent write and commit code, push the result to a branch, and transition the issue to Done. The entire stack is GitHub-native. No Jira, no Claude Code, no Anthropic API key.
The GitHub integration tutorial proved that Sortie can talk to your issue tracker. This tutorial adds three new pieces: a real agent (Copilot CLI), workspace hooks for git operations, and a prompt template that guides the agent through the task.
Prerequisites¶
- GitHub integration tutorial completed — Sortie connects to your GitHub repository and
SORTIE_GITHUB_TOKENis set -
Copilot CLI installed on your machine:
copilot --versionYou should see a version string. If the command is not found, install the Copilot CLI. Node.js 22+ is required.
-
GitHub authentication for Copilot CLI — the adapter checks for tokens in this order:
COPILOT_GITHUB_TOKEN,GH_TOKEN,GITHUB_TOKEN. If none are set, it falls back togh auth status. The fastest path is to reuse the token you already have:export GITHUB_TOKEN="$SORTIE_GITHUB_TOKEN" -
A git repository on GitHub that you can push to, with SSH or HTTPS credentials configured:
git ls-remote git@github.com:yourorg/yourrepo.git HEADYou should see a commit hash. If you get a permission error, fix your SSH or token setup before continuing.
No ANTHROPIC_API_KEY needed. That is the key difference from the Claude Code end-to-end tutorial: Copilot CLI authenticates through GitHub tokens, and a single token can serve both the tracker and the agent.
Create a GitHub issue¶
Create an issue with the backlog label. Pick a task that is concrete and verifiable — the agent reads the description as its primary instruction.
gh issue create --repo yourorg/yourrepo \
--title "Create a health check endpoint" \
--body "Add a /healthz endpoint that returns HTTP 200 with {\"status\": \"ok\"}. Create the handler file and a basic test." \
--label backlog
Note the issue number in the output (e.g., #5). We will see it in the logs later.
Vague descriptions like "improve the API" produce vague results. Concrete tasks — add a file, fix a specific bug, write a test — work best with any coding agent.
Set up the project directory¶
Create a directory for this tutorial, separate from the GitHub integration work:
mkdir sortie-github-e2e && cd sortie-github-e2e
Write the workflow file¶
Create WORKFLOW.md with the full configuration. Replace yourorg/yourrepo with your actual repository:
---
tracker:
kind: github
api_key: $SORTIE_GITHUB_TOKEN
project: yourorg/yourrepo
active_states:
- backlog
- in-progress
- review
handoff_state: done
terminal_states:
- done
polling:
interval_ms: 30000
workspace:
root: ./workspaces
hooks:
after_create: |
git clone --depth 1 git@github.com:yourorg/yourrepo.git .
before_run: |
git fetch origin main
git checkout -B "sortie/${SORTIE_ISSUE_IDENTIFIER}" origin/main
after_run: |
git add -A
git diff --cached --quiet || \
git commit -m "sortie(${SORTIE_ISSUE_IDENTIFIER}): automated changes"
git push origin "sortie/${SORTIE_ISSUE_IDENTIFIER}" --force-with-lease
timeout_ms: 120000
agent:
kind: copilot-cli
command: copilot
max_turns: 3
turn_timeout_ms: 1800000
max_concurrent_agents: 1
copilot-cli:
model: gpt-4.1
max_autopilot_continues: 50
server:
port: 8888
---
You are a senior engineer working in this repository.
## Task
**#{{ .issue.identifier }}**: {{ .issue.title }}
{{ if .issue.description }}
### Description
{{ .issue.description }}
{{ end }}
{{ if .issue.url }}
**Ticket:** {{ .issue.url }}
{{ end }}
## Rules
1. Read existing code before writing anything new.
2. Keep changes minimal — implement exactly what the task requires.
3. Run any available lint and test commands before finishing.
{{ if not .run.is_continuation }}
## First run
Start by understanding the codebase structure. Check for existing patterns
(routing setup, test conventions) and follow them. Write the implementation,
add a test, and verify everything passes.
{{ end }}
{{ if .run.is_continuation }}
## Continuation (turn {{ .run.turn_number }}/{{ .run.max_turns }})
You are resuming. Run `git status` and check test output to understand the
current state. Continue from where the previous turn left off.
{{ end }}
{{ if and .attempt (not .run.is_continuation) }}
## Retry — attempt {{ .attempt }}
A previous attempt failed. Review workspace state and error output before
making changes. Do not repeat the same approach that failed.
{{ end }}
If you followed the Claude Code end-to-end tutorial, this file will look familiar. The hooks and prompt template are nearly identical. The differences are in the tracker and agent configuration.
Tracker: GitHub instead of Jira¶
tracker.kind: github uses the GitHub adapter. The project field takes owner/repo format, and api_key: $SORTIE_GITHUB_TOKEN is a single Bearer token — no email:token format like Jira. State is managed through labels: when Sortie transitions an issue, it removes the old state label, adds the new one, and closes the issue if the target state is terminal. No Jira workflow configuration required.
Agent: Copilot CLI instead of Claude Code¶
agent.kind: copilot-cli uses the Copilot CLI adapter. Where the Claude Code tutorial sets permission_mode: bypassPermissions, Copilot CLI runs with --autopilot, --no-ask-user, and --allow-all by default — no extra permission field needed.
The copilot-cli section is a pass-through to the Copilot CLI binary. max_autopilot_continues: 50 is the inner turn budget, analogous to claude-code.max_turns. With three Sortie turns and 50 autopilot continues each, the agent gets up to 150 total steps to finish the task. model: gpt-4.1 selects the LLM model. Replace it with your preferred model.
Authentication: one token, two jobs¶
SORTIE_GITHUB_TOKEN authenticates Sortie to the GitHub API. GITHUB_TOKEN (or GH_TOKEN, or COPILOT_GITHUB_TOKEN) authenticates Copilot CLI to GitHub's AI backend. They can be the same token. If you ran the export GITHUB_TOKEN="$SORTIE_GITHUB_TOKEN" command from the prerequisites, both are already set.
Workspace and hooks¶
The hooks work the same way as in the Claude Code tutorial: after_create clones the repo, before_run creates a branch from origin/main, and after_run commits and pushes. For a detailed walkthrough of the hook lifecycle and environment variables, see the hooks section in the Claude Code tutorial.
Prompt template¶
The template body is a Go text/template rendered per issue. It branches on three conditions: first run, continuation, and retry. The #{{ .issue.identifier }} prefix uses the # convention because GitHub Issues are referenced as #5, not PROJ-55.
Validate the configuration¶
Check for syntax errors before running:
sortie validate ./WORKFLOW.md
No output means no errors. Confirm with:
echo $?
This should print 0.
Run Sortie¶
Start Sortie:
sortie ./WORKFLOW.md
You should see output similar to this (timestamps and IDs will differ):
level=INFO msg="sortie starting" version=0.x.x workflow_path=/home/you/sortie-github-e2e/WORKFLOW.md
level=INFO msg="database path resolved" db_path=/home/you/sortie-github-e2e/.sortie.db
level=INFO msg="http server listening" address=127.0.0.1:8888
level=INFO msg="sortie started"
level=INFO msg="tick completed" candidates=1 dispatched=1 running=1 retrying=0
level=INFO msg="workspace created" issue_id=5 issue_identifier=5
level=INFO msg="hook started" hook=after_create issue_identifier=5
level=INFO msg="hook completed" hook=after_create issue_identifier=5
level=INFO msg="hook started" hook=before_run issue_identifier=5
level=INFO msg="hook completed" hook=before_run issue_identifier=5
level=INFO msg="workspace prepared" issue_id=5 issue_identifier=5 workspace=…/workspaces/5
level=INFO msg="agent session started" issue_id=5 issue_identifier=5 session_id=…
level=INFO msg="turn started" issue_id=5 issue_identifier=5 turn_number=1 max_turns=3
The agent is now working. A Copilot CLI session typically takes 3–10 minutes depending on the task complexity and model. The agent reads files, writes code, runs tests — each action appears as events in the log at debug level.
Notice that issue identifiers are bare numbers (5, not #5 or PROJ-55). Both Issue.ID and Issue.Identifier are the issue number for the GitHub adapter.
When the agent finishes, you will see:
level=INFO msg="turn completed" issue_id=5 issue_identifier=5 turn_number=1 max_turns=3
level=INFO msg="hook started" hook=after_run issue_identifier=5
level=INFO msg="hook completed" hook=after_run issue_identifier=5
level=INFO msg="worker exiting" issue_id=5 issue_identifier=5 exit_kind=normal turns_completed=1
level=INFO msg="handoff transition succeeded, releasing claim" issue_id=5 issue_identifier=5 handoff_state=done
level=INFO msg="tick completed" candidates=0 dispatched=0 running=0 retrying=0
Here is the full lifecycle, step by step:
- Sortie polled GitHub and found issue #5 with a
backloglabel. after_createcloned the repository intoworkspaces/5/.before_runcreated the branchsortie/5fromorigin/main.- Copilot CLI started a session and worked on the task.
- The agent completed the turn and exited.
after_runcommitted the changes and pushed the branch.- Sortie removed the
backloglabel, addeddone, and closed the issue. - The next poll found zero candidates and went idle.
Press Ctrl+C to stop Sortie.
Verify the results¶
Three things should be visible now: the code in the workspace, the branch on the remote, and the issue state in GitHub.
Check the workspace¶
Look at the git log in the workspace directory:
cd workspaces/5
git log --oneline -5
You should see the agent's commit at the top:
a1b2c3d sortie(5): automated changes
f4e5d6c (origin/main) Initial commit
Check what the agent produced:
git diff HEAD~1 --stat
This shows the files the agent created or modified.
Check the remote branch¶
Back in any directory, verify the branch exists on your remote:
git ls-remote git@github.com:yourorg/yourrepo.git "refs/heads/sortie/5"
You should see a commit hash. The sortie/5 branch is on GitHub, ready for a pull request.
Check GitHub¶
Open the issue in the browser, or check from the command line:
gh issue view 5 --repo yourorg/yourrepo
Verify three things: the issue is closed, the backlog label is gone, and the done label is present.
If the label did not change: check that the labels exist on the repository (Sortie does not create them automatically), review the Sortie logs for error messages, and confirm your token has repo scope.
Check the dashboard¶
Open http://127.0.0.1:8888/ in a browser while Sortie is running. You will see summary cards (running sessions, retry queue, free slots, total tokens) and a run history table showing the completed session with its issue identifier, turn count, duration, and exit status.
What we built¶
We ran the complete Sortie lifecycle with Copilot CLI on GitHub Issues — entirely GitHub-native. One token authenticates both the tracker and the agent. Sortie polled GitHub, cloned the repository, launched the Copilot CLI, let it write and test code, pushed the result to a branch, and closed the issue.
The same orchestration loop powers the Claude Code end-to-end tutorial with a different agent and tracker. Sortie's adapter-agnostic design means swapping copilot-cli for claude-code (or vice versa) is a config change — the prompt template, hooks, and overall flow carry over.
Where to go next:
- Write a prompt template — conditionals, iteration, and template functions for production prompts
- WORKFLOW.md configuration reference — every field, every default, every constraint
- Monitor with Prometheus — token usage, session counts, and retry rates as time-series metrics
- Copilot CLI adapter reference — CLI flags, event stream, and pass-through configuration
- GitHub adapter reference — field mapping, state derivation, and rate limiting
- Scale agents with SSH — remote execution for production workloads