feat: headless Slack workflow runner via ACP (#48)#49
Open
Conversation
Introduces a runner module that enables headless workflow execution driven by Slack, using Cursor's Agent Client Protocol (ACP) for the agent runtime. Each workflow run gets an isolated git worktree and its own agent process, allowing parallel execution. Components: - ACP client: JSON-RPC 2.0 over stdio to `agent acp` - Slack bot: Bolt SDK with Socket Mode, /workflow slash command - Checkpoint bridge: cursor/ask_question <-> Slack interactive messages - Session manager: lifecycle tracking, status streaming to threads - Worktree manager: git worktree creation/cleanup, MCP + permission config Made-with: Cursor
Made-with: Cursor
Cursor CLI emits diagnostic output on stderr that was being treated as an error, causing false-positive noise. Change the stderr handler in AcpClient to emit a 'stderr' event instead of 'error', and wire the new event to console.error in SessionManager for visibility. Made-with: Cursor
Made-with: Cursor
Iterates active sessions and cleans up worktrees/agents before stopping the Slack app on SIGINT/SIGTERM. Made-with: Cursor
- New logger module with daily rotation and 14-file retention - Replace all console.* calls with structured pino logging - Wire stderr events to logger.debug - Add LOG_LEVEL env var support to config schema Made-with: Cursor
- New SessionStore class with sessions table (create/load/update/close) - SessionManager accepts optional store, persists session lifecycle - Stale sessions from previous runs are logged and marked as errored - Add DB_PATH env var to config schema (default: data/runner.db) Made-with: Cursor
- Rename directory prefix from run- to wf-runner- for disambiguation - Rename branch prefix from runner/ to wf-runner/ - Add sweepOrphaned() method that removes stale worktrees on startup - Update tests for new prefix Made-with: Cursor
H1: Log warnings when auto-approving permission requests for tools not
in the known APPROVED_TOOL_TYPES set (session-manager.ts).
H2: Add configurable timeout (default 60s) to JSON-RPC send() method.
Pending promises are rejected with a descriptive error on timeout.
Long-running prompt/followUp calls opt out with timeout=0.
H3: Validate submodule paths against path traversal (worktree-manager.ts)
and validate slash command arguments against allowlisted patterns
(slack-bot.ts).
Made-with: Cursor
Made-with: Cursor
- Upgrade @types/node from ^20 to ^22 for node:sqlite type support - Fix type cast in session-store.ts for updated stmt.all() return type - Fix mcp-server test resource index from "00" to "01" Made-with: Cursor
S1: Revert .engineering submodule pointer to match main — the pointer was updated during planning and is unrelated to the runner feature. S2: Keep mcp-server.test.ts fix (index '00' → '01') — resource '00' never existed, so this fixes a genuinely broken test on main. O1: Remove dead followUp() method from AcpClient — no callers in the codebase, functionally identical to prompt(). Made-with: Cursor
Made-with: Cursor
Covers Slack app creation, environment configuration, starting the runner, executing workflows via slash commands, monitoring, and troubleshooting. Made-with: Cursor
Provides the Slack app configuration (slash commands, bot scopes, Socket Mode) needed to set up the runner's Slack integration. Made-with: Cursor
Made-with: Cursor
Change checkpoint.blocking from z.literal(true) to z.boolean() in the Zod schema and remove "const": true from the JSON schema. Non-blocking checkpoints are now valid. Fixes 22 test failures caused by work-package activities that use blocking: false. Made-with: Cursor
Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a headless Slack workflow runner enabling AI-driven workflow execution triggered from Slack without a GUI. 9 modules (~1,410 LOC) with SQLite persistence, structured logging, graceful shutdown, and orphan worktree recovery.
🎫 Ticket 📐 Engineering 🧪 Test Plan
Motivation
The runner existed as a PoC that kept all session state in an in-memory Map, logged to the console with no file output, and left agent processes and worktrees behind on shutdown or crash. These gaps prevented it from being used for real workflow execution — a runner restart mid-workflow would lose all context, overnight crashes left no diagnostic trail, and orphaned worktrees accumulated on disk.
This PR hardens the runner for merge by adding SQLite state persistence (via
node:sqlite, zero new dependencies), structured pino logging with daily file rotation, and full resource cleanup on both graceful shutdown and crash recovery.Changes
session-store.ts) — SQLite-backed session metadata storage using Node.js built-innode:sqlite; sessions survive runner restartssession-manager.ts) — Lifecycle management for create → run → checkpoint relay → shutdown, with persistence at each state transitionacp-client.ts) — Cursor ACP agent process lifecycle and stdin/stdout protocol handlingslack-bot.ts) — Slack Bolt app setup, slash-command routing, thread messaging via Socket Modecheckpoint-bridge.ts) — Bridges ACP checkpoint events to Slack interactive messages for human-in-the-loop approvalworktree-manager.ts) — Git worktree creation/teardown per session; orphan sweep on startup removes stalewf-runner-*worktreesconfig.ts) — Zod-validated config from environment variableslogger.ts) — pino + pino-roll for rotating structured JSON log files with session contextindex.ts) — Bootstrap, signal handling (SIGINT/SIGTERM), graceful shutdown orchestrationdocs/slack-app-manifest.yml) — Declarative Slack app configuration for the runnerdocs/runner-setup.md) — Installation and configuration instructionsModule Overview
acp-client.tssession-manager.tsworktree-manager.tsslack-bot.tscheckpoint-bridge.tssession-store.tsconfig.tsindex.tslogger.tsKey Capabilities
wf-runner-<id>), preventing interference between concurrent runsnode:sqliteshutdownAll()which cleans up active sessions, closes the DB, and stops the Slack listenerwf-runner-*worktrees from prior unclean exits📌 Submission Checklist
docs/slack-app-manifest.yml)docs/runner-setup.md)🗹 TODO before merging