Skip to main content
An agent drafts a team’s weekly release notes. It lists the pull requests merged in the week and the Linear issues closed, sorts them into groups, and publishes the draft to Notion for a person to review. A cron job can start it every Monday. A job this small needs no VM or sandbox. It runs in a lightweight terminal inside one FastAPI or Express server:
  • Files: /work, mounted in memory for the agent’s scratch files. To keep them, mount a disk folder, an S3 bucket or another VFS there instead.
  • Python: python3, in-process on Monty.
  • CLIs: gh, linear and ntn call GitHub, Linear and Notion with tokens the server holds, so the agent never sees one.
The commands below are what the agent runs in its terminal. They write the notes for the week of December 29, 2025 in example/billing, a sample repository whose team tracks its work in the Linear team BILL.

Server

This is the server from the Overview with three additions: it registers gh, linear and ntn, each with a credential read from an environment variable. Before you start the server, set GITHUB_TOKEN, LINEAR_API_KEY and NOTION_API_KEY. GitHub, Linear and Notion say how to create each one, and Service Credentials says how to read them from a secret store instead. The GitHub token needs read access to the repository, and the Notion integration needs access to the page the notes go under. The three *_API_URL variables are optional: leave them unset to call the public APIs, or set them for GitHub Enterprise or a proxy.
app.py

Usage

Create a terminal, and define run, which sends commands to it and prints what they print. It needs jq:
The commands between SH and SH in the blocks below run in the terminal on the server, not in the shell that calls run. The output shown comes from the sample repository; yours will list your own work.

List merged pull requests

The agent asks GitHub for the pull requests merged from Monday, December 29, to Sunday, January 4. The dates are in the command, so a run on Tuesday still covers that week. It saves the list as JSON in a file and prints only the count and the titles:
Ten pull requests were merged. --limit 100 caps the list, so raise it for a busier repository. The team starts each title with its kind of change: feat, fix and perf are changes a customer sees; chore, build, ci and test are not; a ! marks a breaking change.

List closed issues

The agent lists the team’s Linear issues and keeps the ones in the Done state that were last updated during the same week:
linear issue list prints every issue of the team as JSON, and jq does the filtering. The JSON has no completion date, so the last update stands in for it. An issue that was finished earlier, or is still in progress, is left out.

Group the changes

Some of these describe the same change: pull request #142 says Closes BILL-31 in its description, and #143 says Fixes BILL-34. A short Python file, which runs on Monty inside the server, joins the two lists:
  • A pull request goes into New, Improved or Fixed by its title. A breaking change goes into its own group, first, and uses the first line of its description, which tells customers what to do, or its title when the description is empty.
  • An issue that a pull request names becomes a second link on that pull request’s entry.
  • An issue that no pull request names gets its own entry, grouped by its label.
  • Everything else, such as chores and dependency bumps, is dropped.
Thirteen items became seven entries: four pull requests were chores, and two issues joined the pull requests that closed them.

Write the release notes

The next file turns the groups into Markdown, one heading per group that has entries, and the agent prints the result:
The notes are a draft. The agent can rewrite a line in plainer words with one more write to /work/release-notes.md before it publishes.

Publish to Notion

The team keeps its notes under a Notion page called Release Notes. The agent finds that page’s ID with Notion’s search API, creates a page under it from the Markdown file, and prints the new page’s link to hand to a reviewer. If no page or more than one has that title, jq stops with an error and the agent creates nothing, so it can ask which page to use:
The first heading of the file becomes the page’s title. $page holds the new page’s ID for the rest of the session, so the agent can read the page back and check that every entry arrived:
The agent can then report:
  • Ten pull requests were merged in the week, and three Linear issues were closed. Seven entries are customer-facing; four chores were left out.
  • One change is breaking: the v1 invoices API is gone, and the notes say to use /v2/invoices. It is listed first.
  • The draft is a Notion page under Release Notes, with a link to every pull request and issue, and is waiting for review.
For the commands and flags each CLI supports, see gh, linear and ntn.