chapter: 03·part: The Individual (one workflow)

Anatomy & the Compile Model

Read any workflow's frontmatter + Markdown, run the compile-and-iterate loop, and understand what the generated .lock.yml contains.

Objective

By the end of this chapter you can open any agentic workflow and read it fluently — the YAML frontmatter and the Markdown body — run the compile-and-iterate loop with confidence, and understand what the generated .lock.yml actually contains and why it exists. In Chapter 2 you shipped a workflow; here you open the hood.

This chapter's fixed target is gh aw v0.88.7. We reuse the unchanged Repo Assistant from Chapter 2 — examples/ch02/repo-assistant-triage.md — and read its source alongside target compiler output retained on 2026-09-15. That is compile-time evidence, not a live-run transcript.

Concept: natural language as reviewable source

The most important idea in this chapter is also the most quietly radical: in gh-aw, the prose is the source code. A workflow is a Markdown file with two main parts — a YAML frontmatter block between --- markers that carries configuration, and a Markdown body of natural-language instructions for the agent (tagged Workflow Structure reference). That file lives in .github/workflows/, under version control, and is reviewed in a pull request exactly like any other source file.

Why “reviewable” is the whole point

Traditional automation can bury its intent in implementation details. Making the prose an authored artifact exposes that intent: instead of enumerating every “if issue has label X, do Y,” you can ask the assistant to analyze an issue and provide helpful context. A reviewer can examine the task, examples, and limits without first decoding the generated Actions machinery. Review the configuration too: readable instructions do not replace permission boundaries.

Two distinctions keep this precise:

  • Frontmatter vs. body. Frontmatter is machine-facing configuration (triggers, permissions, engine, tools); the body expresses the intent the agent interprets. They are reviewed together but processed differently.
  • Reviewable is not the same as deterministic. Making the instruction inspectable does not make the agent's response reproducible. Holding those two ideas apart is what the rest of the chapter is about.

Inspectable orchestration is not deterministic inference

The determinism boundary is not “one unpredictable job surrounded by deterministic jobs.” Both the main agent and the default threat detector perform separate, probabilistic AI inference. The detector analyzes proposed output and patches; its judgment is an additional security layer, not an infallible safety verdict (v0.88.7 threat-detection reference).

What you can inspect is the orchestration: job dependencies, permissions, conditions, and output-handling rules. Fixed control logic does not guarantee an identical execution trace — event data, service responses, failures, and both AI judgments still matter. Read the generated jobs below as an execution plan, not a promise of deterministic answers.

Reviewability also extends to dependencies. Shared configuration and prompt files are inputs you must review and version, not a promise that every consumer automatically receives a central edit. The authoring loop distinguishes configuration composition from loading prompt text.

In gh-aw: frontmatter + Markdown to gh aw compile to .lock.yml

A prose file can't run directly on GitHub Actions, and hand-writing the hardened Actions YAML it would need is verbose and easy to get insecurely wrong. So gh-aw inserts a compile step. gh aw compile turns the source into a .lock.yml: it compiles configuration and arranges runtime prompt loading (tagged Compilation Process reference). This makes the execution plan reviewable alongside the intent.

Recipe: compile the Repo Assistant installed in Chapter 2 with the selected v0.88.7 compiler; this does not dispatch a workflow
gh aw compile .github/workflows/repo-assistant-triage.md --strict

Why a compile step exists at all

The compile step earns its place by buying four things at once:

  • Portability. The output is ordinary GitHub Actions YAML, with explicit engine and runtime dependencies rather than a hidden execution plan.
  • Review & validation. Parsing, import resolution, and security checks catch configuration errors before deployment. They do not test the quality of an agent's future answer.
  • Reproducibility. You can track and compare build inputs and emitted artifacts, instead of treating each deployment as an undocumented setup.
  • Pinning & hardening. The compiler emits managed dependency pins and permission-separated jobs. Inspect the actual references, especially custom or imported actions; the dependency excerpt below shows why “every ref becomes immutable” is too strong.

Compilation invokes no AI engine, but it is not universally offline or side-effect-free. Resolving action refs, imports, or packages and performing additional validation can require network access and repository or dependency authentication. Besides the adjacent lock, compilation can create .gitattributes and .github/aw/actions-lock.json, even without --fix. Use a disposable checkout for verification when you need to protect a working tree (tagged compiler implementation; tagged CLI reference).

What makes a build reproducible?

Markdown bytes alone are not the whole input. Record the compiler version, flags and compiler environment, authored configuration and imported dependencies, resolved pins and caches, repository context, and relevant existing lock state. Existing locks can influence preserved deadlines and safe-update review; fuzzy schedules also depend on a repository-derived seed or an explicit --schedule-seed. That flag fixes schedule scattering, not repository-feature validation (target compile options). Review the regenerated diff instead of expecting a universal file size, job count, or compile duration.

Two artifacts, one source of truth

One authored intent has two primary artifacts: the .md is the editable source of truth, and the .lock.yml is the generated Actions workflow. Commit both (tagged file-organization guidance). You edit the Markdown; the lock runs. You never hand-edit the lock — it carries a blunt DO NOT EDIT banner, and a later compile can overwrite your changes. Reviewers can diff the human intent and the generated execution plan.

The authoring loop: compile, status, run, iterate

Authoring a workflow is a feedback cycle, not a one-shot: write → compile → review → (check status) → run → iterate. You rarely get the instructions right the first time, so learn which edits change the execution plan and which only change the runtime instructions.

The fast path and the slow path

For the Repo Assistant's default runtime loading, not every edit requires a new lock:

  • Fast path — edit ordinary body text. A wording change can take effect without recompilation when the next run loads that committed revision of the Markdown. An uncommitted local edit does not change a deployed run.
  • Slow path — change configuration. Triggers, permissions, engine, tools, network settings, and other frontmatter changes require recompilation. This includes configuration contributed by imports.

Configuration composition, runtime loading, and explicit inlining are different operations. An imports: dependency can contribute configuration at compile time while prompt content is still loaded at runtime. Default imports therefore do not make every lock completely self-contained. Explicit inlined-imports: true embeds imported content at compilation; changing that content requires recompilation and deployment of the new lock (tagged inlining reference). The running example does not enable inlining.

A pinned or vendored shared component does not advance when its central source changes. Deliberately update the consumer's dependency, review it, recompile, and deploy. Chapter 11 develops this reuse model; here, the important question is which inputs will this lock read, and when?

Recompile before review, and whenever an edit changes compiled inputs rather than ordinary wording. Compiling body-only edits is also safe and may be required by repository policy (tagged editing guidance). For interactive iteration, gh aw compile --watch can recompile on save; do not use an indefinite watch process as a verification gate.

When to use which check

Compilation, extra validation, and mutation are separate choices in v0.88.7
ChoiceUse it forDo not infer
compile … --strictSource compilation with effective strict validation and an emitted lock. Strict mode already defaults on; the flag forces it over workflow opt-outs.A successful live run, correct AI judgment, or scanner coverage.
Add --validateAdditional Actions-schema, runtime-package, repository-feature, action, and container validation paths.Environment-independent results. Network access and tool availability matter; some checks can be skipped.
Explicit linters/scanners, such as --shellcheckExtra analysis when deliberately requested and available. ShellCheck is opt-in in this target.That --strict or --validate ran every scanner.
--no-emitDiagnosis without generating the workflow lock.An emitted-lock PASS or a promise of no other local/network effects.
--fixDeliberately applying source codemods before compilation, followed by review.A read-only check. This option edits authored sources and was not used for the retained example.

These distinctions follow the tagged compile/scanner configuration, validation implementation, and inspected target CLI help. Preserve exit status, stdout, and stderr: an empty JSON warning array is not sufficient evidence that nothing needs review.

Observe, then run

gh aw list summarizes workflow names, engines, and compilation status without checking deployed workflow state. Its default local mode reads local files; --repo requests remote data. gh aw status also checks GitHub workflow state and can query latest runs with --ref. Do not call both commands universally local or API-free (tagged CLI reference).

Keep the checks in order: verify source compilation, inspect the emitted plan, validate the intended deployment context, then test runtime behavior when authorized. The next section reads that plan without pretending to have executed it.

Worked example: reading a compiled .lock.yml side by side

Let's read the Repo Assistant's source and generated lock. The unchanged examples/ch02/repo-assistant-triage.md passed v0.88.7 compilation with --strict --validate --no-check-update --schedule-seed webmaxru/github-agentic-workflows-book --json on 2026-09-15, with exit zero and an emitted lock. This was an isolated Git checkout using https://github.com/github/gh-aw.git as its reference remote; no source was uploaded or workflow dispatched.

Evidence limit: that reference context has Issues enabled; the book repository does not. This pass is not deployment certification. Docker image validation was skipped in the research environment, optional scanners were not run, and no engine authentication or runtime behavior was tested. Live use needs an appropriate issue context, Issues enabled, the allowed labels present, and engine credentials.

The source: configuration around a task

Authored frontmatter excerpt from examples/ch02/repo-assistant-triage.md — delimiters and body omitted; the complete, unchanged recipe is the Chapter 2 Repo Assistant
on:
  issues:
    types: [opened]
  workflow_dispatch:
permissions:
  contents: read
  issues: read
engine: copilot
network: defaults
safe-outputs:
  add-comment:
    max: 1
  add-labels:
    allowed: [bug, enhancement, question, documentation]
    max: 1

The body asks the assistant to categorize the issue, post one short triage comment, and apply at most one allowed label; vague issues get a request for more information instead of a label. The frontmatter supplies the machine-enforced configuration around that judgment. We now follow those settings into excerpts of the actual emitted lock, not a replacement workflow to copy.

The header: provenance and a hash

Generated header excerpt: the complete metadata line from the retained v0.88.7 triage lock; remaining header omitted
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"a783ee7316147865b8aee02ec2873beec4319cbc73914340f25e303f1ea7b9cb","body_hash":"8c11b173a5204ea2a0b0768f9fe40e57418b0c35ab2cf4b56b391267550a377b","compiler_version":"v0.88.7","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.80"}}

The capture confirms metadata schema v4, compiler v0.88.7, and effective strict: true. The source says only engine: copilot; the compiler-selected default CLI version recorded here is 1.0.80, also confirmed by the retained defaults probe. A compiler default is not a measurement of an installed runtime or proof that runtime overrides were absent.

The frontmatter_hash and body_hash fingerprint source at compilation. They are useful provenance, not a complete description of every build input, nor a guarantee of identical answers. In particular, a recorded body hash does not make later runtime-loaded prompt text immutable. The next header line, gh-aw-manifest, records secret references and dependencies; the banner says DO NOT EDIT. These are review aids, not a security verdict or proof that every listed secret must be configured.

Dependencies: inspect the refs that were emitted

Generated dependency-comment excerpt from the same triage lock — two adjacent entries, not the complete manifest
#   - actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
#   - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1

Here, each action ref is a full commit SHA with a readable version comment. That is stronger than a movable tag, but do not generalize these two entries to every imported action. A separate 2026-09-16 compile-only probe of the canonical APM v0.28.0 shared component passed strict source compilation while retaining microsoft/apm-action@v1.10.0; its manifest recorded a resolution failure and its stderr retained review warnings. APM is an independently versioned integration, not part of this triage recipe.

Inspect custom and imported uses: refs, dependency manifests, and diagnostics. Where immutable refs are required, review and pin the authored or vendored dependency, then recompile — do not patch the generated lock or weaken strict mode. Chapter 11 continues that dependency-review discipline.

The prompt: runtime loading is visible too

Generated environment-entry excerpt from activation's “Create prompt with built-in context” step; enclosing YAML omitted
          GH_AW_PROMPT_CONTENT_0005: "{{#runtime-import repo-assistant-triage.md}}\n"

The compiled plan contains a runtime import of the authored Markdown, not just a frozen copy of its body. This is the concrete reason to keep the source available and to distinguish the body-edit fast path from configuration changes and explicit inlining.

The job graph: read boundaries, not a fixed job count

The capture has top-level permissions: {} and explicit scopes on individual jobs. Read needs and if, not the order in which job names happen to appear in the YAML. This Repo Assistant includes pre-activation membership checks, activation/context preparation, the main agent, detection, safe-output processing, and conclusion/reporting. Other configurations can produce a different graph (tagged job-construction reference).

First locate the two separate inference sites. These are actual step prefixes from different jobs; their remaining commands and environment are deliberately omitted.

Generated step-prefix excerpt from the agent job — the main agent executes the task here
      - name: Execute GitHub Copilot CLI
        id: agentic_execution
        # Copilot CLI tool arguments (sorted):
        timeout-minutes: ${{ fromJSON(vars.GH_AW_DEFAULT_TIMEOUT_MINUTES || '20') }}
Generated excerpt from the detection job — binary installation and the following inference-step prefix; the inference command body is omitted
      - name: Install threat-detect binary
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        continue-on-error: true
        run: |
          bash "${RUNNER_TEMP}/gh-aw/actions/install_threat_detect_binary.sh" v0.5.1
      - name: Execute threat detection with AWF
        id: detection_agentic_execution
        if: always() && steps.detection_guard.outputs.run_detection == 'true'
        continue-on-error: true
        timeout-minutes: 10

v0.88.7 uses external threat-detect as the default detector implementation; this lock installs v0.5.1 and its later command invokes it with the Copilot engine. It is AI analysis, separate from the main agent's inference, not a deterministic linter. Notice the guard and error-handling settings too: a dependency on a detection job is not by itself proof that analysis ran or that content is safe (tagged detection contract).

Next locate the write boundary. In this capture the main agent has contents: read and issues: read; detection has contents: read. Requested comments and labels are processed in a separate job with write scopes:

Generated safe_outputs job-prefix excerpt from the retained lock — actual dependencies, condition, and permissions; remaining job omitted
  safe_outputs:
    needs:
      - activation
      - agent
      - detection
    if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success'
    runs-on: ubuntu-slim
    permissions:
      issues: write
      pull-requests: write

The if expression checks job eligibility; it is not a natural-language assertion that every requested item is harmless. Follow the detector result and the output handler's checks as well. Programmed handlers mediate writes, but an authorized comment can still be wrong and an API operation can fail. The mechanism is Chapter 6; the layered threat model is Chapter 7.

Recap & what's next

You can now read a workflow and its compiled output with a clear model of each:

  • A workflow is natural language as reviewable source — YAML frontmatter (config) plus a Markdown body (intent), committed and reviewed like code.
  • gh aw compile is a real build step that invokes no engine, but can use the network and write supporting files. Reproducibility depends on more than Markdown bytes.
  • You commit two primary artifacts — edit the .md, never hand-edit the .lock.yml — and preserve their required dependencies.
  • The authoring loop separates compiled configuration, runtime-loaded wording, and explicit inlining. Shared dependency updates are deliberate consumer changes.
  • Strict compilation, extra validation, scanners, and live testing are distinct evidence. Inspect emitted metadata, actual refs, and diagnostics rather than inferring more than a check proves.
  • The determinism boundary separates inspectable orchestration from judgment: both the agent and default detection paths perform AI inference. Neither a stable graph nor a detector verdict guarantees a correct, safe answer.

What's next. You've read the workflow's anatomy; next comes event selection and admission. In Chapter 4: Triggers, we open the on: block to choose which events can request a Repo Assistant run and which admission checks apply.