> ## Documentation Index
> Fetch the complete documentation index at: https://docs.guild.ai/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> To act on a user's behalf in Guild, use the Guild REST API at https://api.guild.ai/v1.
> The OpenAPI spec is unauthenticated: https://docs.guild.ai/openapi.json (or openapi.yaml).
> Authenticate with an account API key, sent as HTTP Basic (key id as username, secret as password) or as `Authorization: Bearer <api_key_id>:<api_key_secret>`. See https://docs.guild.ai/api-reference/introduction#authentication.
> To run one agent on demand without account-wide access, use an API trigger instead: https://docs.guild.ai/platform/api-triggers.
> To search these docs from an MCP client, connect to https://docs.guild.ai/mcp. The full page index is https://docs.guild.ai/llms.txt.

# Post release notes to Slack from CI

> Build a Guild agent your CI calls on each release tag. It turns the commits since the last tag into release notes and posts them to Slack, using an API trigger.

This recipe builds an agent that your CI pipeline runs. When you push a release tag, a GitHub Actions job collects the commits since the previous tag and sends them to Guild through an [API trigger](/platform/api-triggers). The agent writes release notes from them and posts the notes to a Slack channel. It uses a [Native agent](/guide/native-agents): you write a prompt and a tool list, no code.

## Before you start

* The [Guild CLI](/cli/getting-started), logged in.
* Slack connected in your workspace, and the Slack agent invited to the channel it will post in. See [Slack setup](/integrations/slack#setup).
* A GitHub repository where you can add Actions workflows and secrets.

## 1. Create the agent

```bash theme={null}
guild agent init --name release-notes --agent-type GUILD_NATIVE
cd release-notes
```

Replace `PROMPT.md`:

```markdown theme={null}
You write release notes and post them to Slack.

Your input names a release, a Slack channel ID, and the commit subjects
since the previous release, one per line.

1. Group the commits under the headings New, Fixed, and Other. Rewrite each
   one as a short sentence a user would understand. Drop commits that only
   change tests, CI, or formatting.
2. Post one message with slack_chat_post_message to the channel: the release
   name, then the groups. Leave out empty groups.
3. If there are no commits, post "<release>: no changes." instead.
```

Replace `guild.yaml`:

```yaml theme={null}
integrations:
  - name: guildai~slack
    version: ^2.0.0
    tools:
      - slack_chat_post_message
```

The agent gets the commits from its input, so it needs no GitHub tools.

## 2. Save it and install it in your workspace

```bash theme={null}
guild agent save --message "First version" --publish
guild workspace agent add <your-name>~release-notes
```

## 3. Create the API trigger

API triggers and their keys are created in the web UI:

1. Open the workspace, click **Triggers**, then **Add Trigger**.
2. Select **API**, choose `<your-name>~release-notes`, and name it, for example `Release notes from CI`.
3. Copy the credentials string, `<api_key_id>:<api_key_secret>`. It's shown only once.

In your GitHub repository, add the string as an Actions secret named `GUILD_TRIGGER_KEY`. The key can read and post to every session in the workspace, so keep it out of logs and code.

## 4. Add the workflow

Add `.github/workflows/release-notes.yml`, with your Guild workspace's owner and name and the Slack channel ID filled in:

```yaml theme={null}
name: Release notes

on:
  push:
    tags: ["v*"]

jobs:
  release-notes:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Ask Guild for release notes
        env:
          GUILD_TRIGGER_KEY: ${{ secrets.GUILD_TRIGGER_KEY }}
          GUILD_WORKSPACE: <owner>/<workspace>
          SLACK_CHANNEL: <channel-id>
        shell: bash
        run: |
          if prev=$(git describe --tags --abbrev=0 "$GITHUB_REF_NAME^" 2>/dev/null); then
            range="$prev..$GITHUB_REF_NAME"
          else
            range="$GITHUB_REF_NAME"
          fi
          commits=$(git log -n 200 --no-merges --format='%s' "$range")
          text=$(printf 'Release: %s. Slack channel: %s.\n\nCommits (%s):\n%s' \
            "$GITHUB_REF_NAME" "$SLACK_CHANNEL" "$range" "$commits")
          jq -n --arg text "$text" \
            '{session_type: "api_trigger", agent_input: {text: $text}}' > body.json
          curl --fail-with-body -sS -X POST \
            "https://api.guild.ai/v1/workspaces/$GUILD_WORKSPACE/sessions" \
            -u "$GUILD_TRIGGER_KEY" \
            -H "Content-Type: application/json" \
            -d @body.json | jq -r .session_url
```

`fetch-depth: 0` fetches the history and tags that `git describe` needs. On the first tag there's no previous one, so the job lists every commit up to it. `shell: bash` turns on `pipefail`, so a failed request fails the step instead of passing `null` along.

A Native agent takes text, so `agent_input` is `{"text": ...}`. `jq` builds the body so quotes and newlines in commit messages stay valid JSON.

## 5. Try it

Push a tag:

```bash theme={null}
git tag v0.1.0
git push origin v0.1.0
```

The job prints the session URL as soon as Guild accepts the request, and the notes appear in the Slack channel when the agent finishes. Open the session URL to see what the agent did.

## Variations

* **Fail the job when the run errors.** Save the session ID instead of printing the URL (`session=$(curl ... | jq -r .id)`), then poll [session status](/platform/api-triggers#retrieve-session-status) until `root_task.status` is `DONE`, `ERROR` or `INTERRUPTED`:

  ```bash theme={null}
  for _ in $(seq 60); do
    status=$(curl -sS -u "$GUILD_TRIGGER_KEY" \
      "https://api.guild.ai/v1/sessions/$session" | jq -r .root_task.status)
    case "$status" in
      DONE) exit 0 ;;
      ERROR|INTERRUPTED) echo "Agent finished with $status"; exit 1 ;;
    esac
    sleep 10
  done
  echo "Timed out waiting for the agent"; exit 1
  ```

  `WAITING` isn't terminal: the task waits there on sub-agents or hooks. A failed Slack post doesn't make the run fail: the agent sees the tool's error and can still finish `DONE`. To catch that too, tell the agent to say in its final answer whether the post succeeded, and read it from the [session events](/platform/api-triggers#fetch-session-events-logs-and-thoughts).
* **Run it after a deploy instead.** Call the same endpoint from your deploy job, with the commits between the previous and the new deployed SHA.
* **Use one trigger for several agents.** Add `"agent_id": "<owner>~<agent>"` to the body to run another agent installed in the workspace with the same key. `agent_input` must then match that agent's input.

## Related

* [API triggers](/platform/api-triggers): keys, follow-up messages, status and events
* [Slack integration](/integrations/slack): posting messages and scopes
* [Native agents](/guide/native-agents): what a prompt-only agent can and can't do
