Skip to content

Commit eb416dd

Browse files
Initial durable GitHub Mastra workflow
0 parents  commit eb416dd

18 files changed

Lines changed: 3653 additions & 0 deletions

.env.example

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
PORT=4317
2+
DATABASE_PATH=.data/events.sqlite
3+
MASTRA_DATABASE_URL=file:.data/mastra.sqlite
4+
# Leave empty for the loopback-only self-hosted-runner setup. Set this when a
5+
# real GitHub webhook reaches the service through a tunnel.
6+
GITHUB_WEBHOOK_SECRET=
7+
GITHUB_BOT_LOGIN=loop-engineering-bot
8+
MAX_ACTIVE_IMPLEMENTATIONS=1
9+
SIMULATED_IMPLEMENTATION_MS=250

.github/workflows/local-loop.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Send event to local Mastra loop
2+
3+
on:
4+
issues:
5+
types: [opened, edited, labeled, reopened]
6+
issue_comment:
7+
types: [created]
8+
pull_request:
9+
types: [opened, synchronize, reopened]
10+
push:
11+
12+
jobs:
13+
enqueue:
14+
# Install a GitHub self-hosted runner on the machine that runs Mastra and
15+
# give it the custom label "local-llm".
16+
runs-on: [self-hosted, local-llm]
17+
timeout-minutes: 2
18+
steps:
19+
- name: Deliver event to the durable local inbox
20+
env:
21+
DELIVERY_ID: ${{ github.run_id }}-${{ github.run_attempt }}
22+
EVENT_NAME: ${{ github.event_name }}
23+
EVENT_PATH: ${{ github.event_path }}
24+
run: |
25+
curl --fail-with-body --retry 3 \
26+
-X POST http://127.0.0.1:4317/webhooks/github \
27+
-H "content-type: application/json" \
28+
-H "x-github-delivery: ${DELIVERY_ID}" \
29+
-H "x-github-event: ${EVENT_NAME}" \
30+
--data-binary "@${EVENT_PATH}"

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
node_modules/
2+
dist/
3+
.data/
4+
.env
5+
*.log
6+
.DS_Store

README.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# Durable GitHub → Mastra loop
2+
3+
This is the small control plane we discussed for the webinar. GitHub events are
4+
recorded in SQLite first and acknowledged quickly. A coordinator then gives
5+
Mastra at most one implementation slot by default. Consequently, an event that
6+
arrives while Mastra is working is never used to interrupt the current model
7+
turn.
8+
9+
For an event concerning the same issue, the coordinator attaches it to the
10+
existing run's inbox. For a different issue it creates another run, which waits
11+
for the local implementation slot. GitHub delivery IDs make retries harmless,
12+
and messages produced by the configured bot are ignored to prevent feedback
13+
loops.
14+
15+
```mermaid
16+
flowchart LR
17+
GH["GitHub event"] --> IN["Durable SQLite inbox"]
18+
IN --> ROUTE{"Existing run for issue?"}
19+
ROUTE -->|yes| ATTACH["Attach to its inbox"]
20+
ROUTE -->|no| QUEUE["Create queued run"]
21+
ATTACH --> WAIT{"Waiting for human?"}
22+
WAIT -->|human comment| RESUME["Resume same Mastra run"]
23+
WAIT -->|currently working| BOUNDARY["Read at safe step boundary"]
24+
QUEUE --> SLOT["Acquire implementation slot"]
25+
SLOT --> MASTRA["Mastra workflow"]
26+
RESUME --> MASTRA
27+
```
28+
29+
## Run it locally
30+
31+
Node 24 and pnpm are required.
32+
33+
```sh
34+
pnpm install
35+
cp .env.example .env
36+
pnpm start
37+
```
38+
39+
The service listens only on `127.0.0.1:4317`. `GET /health` shows whether an
40+
implementation occupies the slot, and `GET /runs` shows the durable run state.
41+
The event database and Mastra's workflow snapshots live separately under
42+
`.data/`.
43+
44+
The included [GitHub Actions workflow](.github/workflows/local-loop.yml) uses a
45+
self-hosted runner labelled `local-llm`. That runner opens an outbound
46+
connection to GitHub, receives the job, and posts the event to the loop service
47+
on the same machine. Therefore the local machine does not need a public inbound
48+
port. For a direct GitHub webhook instead, expose the receiver through a secure
49+
tunnel and set `GITHUB_WEBHOOK_SECRET`; the receiver validates
50+
`x-hub-signature-256` against the raw body.
51+
52+
## Human decisions
53+
54+
Add the label `needs-human` to an issue to demonstrate suspension. Mastra stores
55+
the suspended workflow snapshot and the control database marks the run as
56+
`waiting_human`. A subsequent human issue comment is correlated with that issue
57+
and resumes the exact Mastra run. Other issues may proceed while this one waits.
58+
59+
## Where Codex fits
60+
61+
The current implementation step deliberately waits for
62+
`SIMULATED_IMPLEMENTATION_MS`; this makes concurrency behavior deterministic in
63+
the demo and tests. Replace that delay inside `src/workflow.ts` with the Codex
64+
goal invocation. The durable inbox, one-writer rule, suspension, and event
65+
routing stay unchanged. Review agents can then be added after implementation as
66+
parallel Mastra branches, followed by the manager/consolidation node we designed.
67+
68+
## Verify
69+
70+
```sh
71+
pnpm typecheck
72+
pnpm test
73+
```
74+
75+
The integration tests cover the critical race: a second delivery arrives while
76+
the first Mastra run is active, attaches to that run, and does not create a
77+
second implementation. They also exercise real Mastra suspension and resumption
78+
from a GitHub comment.

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "github-mastra-loop-prototype",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"dev": "node --watch --import tsx src/server.ts",
8+
"start": "node --import tsx src/server.ts",
9+
"typecheck": "tsc --noEmit",
10+
"test": "node --import tsx --test test/**/*.test.ts"
11+
},
12+
"dependencies": {
13+
"@mastra/core": "1.55.0",
14+
"@mastra/libsql": "1.18.0",
15+
"zod": "^4.3.6"
16+
},
17+
"devDependencies": {
18+
"@types/node": "^24.10.0",
19+
"tsx": "^4.21.0",
20+
"typescript": "^5.9.3"
21+
},
22+
"engines": {
23+
"node": ">=24"
24+
},
25+
"packageManager": "pnpm@11.5.2"
26+
}

0 commit comments

Comments
 (0)