We Built a Slack-Native AI Agent for the Full Dev Lifecycle
How Axon takes a team from Jira ticket to merged PR.
We Built a Slack-Native AI Agent for the Full Dev Lifecycle
How Axon takes a team from Jira ticket to merged PR.


The Problem
Our team had a growing backlog. Not because we lacked engineers — but because the cost of context-switching between planning, coding, reviewing, and deploying ate into everyone’s deep work time. A developer would spend 30 minutes reading a Jira ticket, 20 minutes understanding the codebase context, and then finally start writing code.
We asked ourselves: what if an AI agent could handle the mechanical parts — the context-gathering, the initial implementation, the test-run-fix loop — while humans stayed in the loop for decisions that actually matter?
That’s how Axon was born.
What Axon Does
Axon is a Slack-native AI agent backed by LangGraph and FastAPI. It lives in your Slack workspace and handles four distinct workflows:
1. Conversational Agent
Ask Axon questions about your codebase, run gh CLI commands, look up service architecture, or generate reports — all from a Slack thread. Every thread is an isolated session with full conversation history persisted in Postgres.
2. Jira → GitHub Issue Conversion
Say @axon use jira-to-github for <jira-url> and an Opus-powered sub-agent with extended thinking kicks in. It fetches the Jira ticket, loads your service registry for architecture context, generates enriched GitHub issues with proper labels and cross-references, and asks for your approval before creating anything.
The service registry is what makes this more than a copy-paste. It’s a JSON file that maps every service to its dependencies — what SNS topics it publishes, what queues it consumes, which other services call it via REST, and key file paths in the codebase. When the sub-agent generates a GitHub issue, it can say “this change touches EntityService, which publishes EntityUpdated events consumed by NotificationService and ReportingService" — giving reviewers the full blast radius without grepping through 15 repos themselves.
The registry is auto-generated by a sync-and-rebuild.sh script that clones every repo, scans for SNS publish patterns, SQS consumers, and REST client references, then assembles the dependency graph. Run it on a cron or trigger it from Slack (@axon sync repos) and the agent always has a current picture of your architecture.
Two human approval gates ensure nothing ships without a pair of eyes on it.
3. Autonomous Coding (@axon implement)
This is the headline feature. Type:
@axon implement https://github.com/your-org/your-repo/issues/42
And Axon:
- Validates the issue exists and the repo is configured
- Dispatches a Sandcastle coding agent
- The agent reads the issue, plans the implementation, writes code, runs the build and tests, fixes failures, and opens a PR
- Posts live progress updates to your Slack thread as it works
- Delivers the PR URL when done
The entire flow — from Slack message to open PR — typically completes in 15–30 minutes depending on complexity.

Implement Process
4. PR Review Feedback (@axon review)
After a human reviews the PR, post @axon review in the same Slack thread. Axon reads every open review comment, pushes targeted fixes, runs the test suite again, and replies to each comment on GitHub with the fixing commit SHA.
No more “addressed in latest push” without knowing which commit fixed what.

Review Process
Human-in-the-Loop: The Core Pattern
Every production AI agent needs guardrails. Ours is built on a single primitive: LangGraph’s interrupt() function.
When the agent needs a decision — confirming which services are affected, approving generated issue text, or clarifying an ambiguous request — it calls a tool that pauses execution entirely:
@tool
def ask_human(question: str) -> str:
"""Pause execution and wait for human input."""
response = interrupt({"type": "human_input", "message": question})
return str(response)
This isn’t polling. The agent’s entire state — graph position, tool call history, accumulated context — is checkpointed to Postgres and the process stops. When the human replies in the Slack thread, the system calls resume() with their answer, rehydrates the checkpoint, and continues exactly where it left off.
The key design choice: the agent classifies each reply as approval, rejection, edit, or clarifying question. If the human asks “wait, does this also affect the notification service?” instead of saying “yes”, the agent answers the question and then re-asks for approval. The loop continues until a clear decision is made — no silent assumptions.
In the Jira→GitHub flow, there are two HITL gates:
- Service confirmation — “I identified these 3 services as affected. Correct?”
- Issue approval — “Here are the GitHub issues I’ll create. Approve?”
In the coding flow, HITL lives at the PR review stage — the agent implements autonomously, but a human reviews and approves the merge.

Multiple HITL before publish the issue
Per-Ticket Workspace Isolation
Concurrent coding runs on the same repo need isolation. Our solution: every @axon implement creates a dedicated workspace directory.
When a run starts, the agent:
- Derives a ticket key from the issue URL (e.g.
your-org__your-repo-issue-42) - Clones the repo fresh into
/workspace/tickets/<ticket_key>/<repo_name>/with--depth=1 - Scaffolds the
.sandcastle/directory with the coding agent templates - Dispatches the Sandcastle run against this isolated clone
/workspace/
├── repos/ # shared reference clones (used for code search)
├── tickets/
│ ├── org__repo-issue-42/
│ │ └── MyService/ # fresh clone for this run
│ ├── org__repo-issue-55/
│ │ └── MyService/ # separate clone, concurrent run
│ └── org__other-repo-issue-12/
│ └── OtherService/
└── service-registry.json
This means:
- No branch collisions — each run works on its own branch in its own directory
- No git lock contention — parallel runs never fight over
.git/index.lock - Clean state — a failed run’s mess is contained;
@axon clean updeletes just that ticket's directory - Resume-friendly — the workspace survives container restarts (it’s a bind-mounted volume), so
resumepicks up the exact same clone
Architecture

The system runs as a set of Docker containers:
- Agent — FastAPI + LangGraph. Handles Slack events, orchestrates tools, persists state.
- Postgres — Thread checkpoints, conversation history, run tracking.
- Sandcastle — Node.js service that dispatches autonomous coding runs.
- LLM Gateway — Routes requests to Claude (Sonnet for chat, Opus for planning).
Key Design Decisions
Thread-as-session. Every Slack thread maps to an isolated agent session with its own Postgres checkpoint. Conversations survive container restarts. Follow-ups in the same thread automatically resume context.
Per-thread serialisation. Messages within the same thread are processed one-at-a-time via asyncio.Lock. Messages across threads run concurrently. This prevents race conditions without sacrificing throughput.
Human-in-the-loop via LangGraph interrupt(). When the agent needs a decision, it pauses execution and surfaces a question in Slack. The next message in the thread resumes the agent with the answer. No polling, no webhooks to configure — the interrupt/resume pattern is built into the graph.
Fire-and-forget coding runs. The trigger_sandcastle tool dispatches a coding run and returns immediately. Progress updates flow back via HTTP callbacks to a dedicated endpoint, which posts them to the Slack thread. The agent doesn't block — it can handle other threads while Sandcastle works.
Shared workspace volume. A bind-mounted volume (/workspace) is accessible to both the agent and Sandcastle containers. Repos cloned by the agent are immediately available to the coding runner. State files (like .resume-state.json) enable resumption across failures.
Observability: Logs Over Slack
A coding agent run generates hundreds of lines of output — Claude Code thinking, build output, test results, git operations. We can’t post all of this to Slack. Slack’s API has rate limits (roughly 1 message per second per channel for chat.postMessage), and even if we could, flooding a thread with 200 messages makes it unreadable.
Our approach:
Slack gets phase transitions only. The Sandcastle runner posts ~4–5 messages per run at key milestones: starting, planning complete, implementing, PR opened (or failed). These are the messages humans actually care about.
Full agent output goes to CloudWatch. Every line from the coding agent — the LLM reasoning, tool calls, build/test output — is written to structured logs. The Sandcastle container tails the agent’s log file and pipes each line to stdout with a label prefix ([sandcastle/issue-42-fix-archive-bug]), which Docker's logging driver forwards to CloudWatch. When something goes wrong, we have the full transcript without digging through Slack.
The agent container does the same. LangGraph tool calls, LLM responses, and the full conversation stream are logged via Python’s logging module with structured prefixes ([stream:TICKET-123]). All of this lands in CloudWatch, searchable by thread ID or ticket key.
This split — Slack for humans, CloudWatch for debugging — means we never hit rate limits and never lose observability.
The Coding Agent in Detail
The autonomous coding flow is where things get interesting. Here’s what happens under the hood when you say @axon implement:

- Validation. The agent parses the issue URL, confirms the repo is configured with a
.sandcastle/directory, and fetches the issue title and body. - Dispatch. A POST request to the Sandcastle service triggers the run. The entrypoint is dynamically patched to target exactly one issue (no planner phase needed).
- Implementation. The coding agent (Claude Code under the hood) reads the issue, explores the codebase, writes the implementation, and iterates through build/test cycles until green.
- PR creation. On success, the agent creates a branch (
sandcastle/issue-{n}-{slug}), pushes, and opens a PR linking back to the issue. - Callbacks. Each phase transition fires an HTTP callback that posts a status update to your Slack thread in real-time.
Failure Handling
Runs fail. Models hallucinate. Tests don’t pass. We designed for this:
- On failure: Axon posts the error and the branch name to Slack. Work is never lost — commits on the branch are preserved.
- Resume: Say
resumein the same thread. The coding agent picks up from its last checkpoint (Claude Code session state) rather than starting fresh. - Cancel: Say
cancelorstopin the thread. The entire process tree is killed, workspace preserved, ready for retry. - Concurrent safety: Multiple implement runs on the same repo work on separate branches with separate entrypoint files. No shared mutable state.
Cost Tracking
We track LLM costs through our gateway dashboard using a dedicated API key for Axon. Every request — whether it’s the conversational agent, the Jira→GitHub sub-agent, or Sandcastle’s coding runs — routes through the same gateway with Axon-specific credentials. This gives us per-day and per-workflow cost breakdowns without instrumenting anything in application code.
The headline number: a typical @axon implement run (well-scoped issue, one service, passing tests) costs $2–12 in LLM tokens. Compare that to 2–4 hours of developer context-switching for the same issue and the ROI math writes itself.
Security
An AI agent that can write code and push to your repos needs tight controls. Here’s how we handle it:
Port separation. The system exposes two ports: port 8000 (public) handles only the Slack webhook and health check. Port 8001 (private, VPN-only) handles all internal endpoints — /invoke, /resume, /implement, /callbacks. The public surface area is exactly one endpoint.
Slack signature verification. Every request to /events/slack is verified using HMAC-SHA256 via Slack's signing secret. Replayed or forged requests are rejected (5-minute replay window). This is handled by the slack-bolt framework — no custom crypto.
Internal API key. All inter-container communication (agent ↔ sandcastle callbacks) is authenticated with a shared secret (INTERNAL_API_KEY) sent as an X-Internal-Token header. Any request to a non-public path without the correct token gets a 403. In production, this is a 256-bit random value.
Tool sandboxing. The agent’s run_gh tool only allows gh CLI commands — arbitrary shell execution is blocked. The input is parsed with shlex.split() and the first token must be gh or the call is rejected. File operations are confined to the /workspace virtual path.
Least-privilege tokens. The GitHub PAT is scoped to repo + issues only. The Slack bot token has the minimum scopes needed. LLM gateway credentials are Axon-specific — revocable without affecting other services.
Network isolation. In production (AWS), the security group allows inbound 443 only from Slack’s published IP ranges and the team VPN. All internal ports are blocked from the internet entirely.
What We Learned
Human-in-the-loop is non-negotiable for production. Early prototypes that ran fully autonomously produced impressive demos but terrifying production incidents. The approval gates in the Jira→GitHub flow and the PR review step in the coding flow aren’t overhead — they’re the product.
Thread persistence changes everything. Being able to resume a conversation — or a coding run — after a container restart, a network blip, or simply overnight makes the difference between a toy and a tool.
The “boring” parts matter most. The LLM calls are 20% of the system. The other 80% is queue management, process lifecycle, failure recovery, concurrent run isolation, and Slack UX polish. An AI agent that can’t be cancelled or resumed is an AI agent nobody trusts.
Start with one repo, prove value, expand. We didn’t try to make Axon work on every repository on day one. We picked one well-tested service, got the implement flow reliable, then expanded repo by repo.
Results
After running Axon in production for several weeks:
- Issue-to-PR time dropped from days (waiting for developer context-switch) to minutes for well-scoped issues
- Review turnaround improved —
@axon reviewaddresses mechanical feedback (naming, formatting, missing null checks) immediately, letting humans focus on design-level comments - Context-switching reduced — developers stay in Slack, approve or reject, and move on. No IDE-switching for routine issues.
The agent doesn’t replace developers. It handles the mechanical parts so developers can focus on the parts that actually require human judgment.
Stack
For those who want to build something similar:
- Agent framework: LangGraph (graph-based agent orchestration with built-in persistence and interrupts)
- HTTP layer: FastAPI (async, split public/private ports)
- LLM: Claude Sonnet (chat) + Claude Opus (planning/conversion)
- Coding agent: Sandcastle (autonomous coding framework)
- State: PostgreSQL (checkpoints, thread contexts, run tracking)
- Slack integration: Slack Bolt for Python
- Infrastructure: Docker Compose (local), extensible to ECS/EKS
What’s Next
We’re exploring:
- Multi-repo issues — a single ticket that requires coordinated changes across services
- E2E testing — after the coding agent opens a PR, automatically running API test collections (e.g. Swagger/Postman collections via Newman) against the changed service and reporting pass/fail back to the PR before merge
- Automated triage — Axon suggesting which issues are good candidates for autonomous implementation
Axon started as a weekend experiment: “What if I could just tell Slack to implement this issue?” A few months later, it’s handling real work. The lesson: the gap between AI demo and AI tool is mostly plumbing — and the plumbing is worth building.

References
- LangGraph — agent orchestration framework with built-in persistence and human-in-the-loop
- Sandcastle — autonomous coding agent framework
- FastAPI — async Python web framework
- Slack Bolt for Python — Slack app framework
- Claude (Anthropic) — LLM powering both conversational and coding agents
- LangGraph Human-in-the-Loop — interrupt/resume pattern docs
메타데이터
- post_id
- c26ec14fedde
- slug
- we-built-a-slack-native-ai-agent-for-the-full-dev-lifecycle-c26ec14fedde
- url
- https://medium.com/@ysc0423/we-built-a-slack-native-ai-agent-for-the-full-dev-lifecycle-c26ec14fedde
- canonical_url
- https://medium.com/@ysc0423/we-built-a-slack-native-ai-agent-for-the-full-dev-lifecycle-c26ec14fedde
- author_url
- https://medium.com/@ysc0423
- status
- ok
- fetched_at
- 2026-08-11 06:36:52