DevOps Pipeline¶
How code gets from idea to production, and how automation grows over time.
Work Management¶
How product work and engineering work flow through different tools and connect.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
D["👤 Daniel / PM"]
DEV["👤 CTO / Developer"]
SLACK["Slack"]
D -->|"creates"| F
DEV -->|"discusses and refines"| F
DEV -->|"plans and creates"| CLI
D <-->|"communicates"| SLACK
DEV <-->|"communicates"| SLACK
subgraph Product ["Asana/ClickUp"]
F["Features + User Stories"]
QUEUE["Prioritized queue"]
OTHER["UX/UI · Business · Operations"]
F -->|"ready to build"| QUEUE
end
CLI["Claude Code CLI"]
CLI -->|"reads priorities"| QUEUE
CLI -->|"creates milestones + tasks"| GH
subgraph Rig ["Automated Engineering Rig"]
GH[GitHub Issues]
AGENTS["Agents · Build · Test · Review · Human Gate · Deploy"]
PROD[Production]
GH --> AGENTS
AGENTS --> PROD
end
PROD -->|"updates status"| F
PROD -->|"usage data"| F
AGENTS <-->|"communicates"| SLACK
style D fill:#f59e0b,color:#000
style DEV fill:#38bdf8,color:#000
style SLACK fill:#e9a820,color:#000
style F fill:#fbbf24,color:#000
style QUEUE fill:#fbbf24,color:#000
style OTHER fill:#fbbf24,color:#000
style CLI fill:#60a5fa,color:#000
style GH fill:#60a5fa,color:#000
style AGENTS fill:#a78bfa,color:#000
style PROD fill:#34d399,color:#000
Product work (features, user stories) lives in Asana/ClickUp where Daniel and product people work. When a feature is marked "ready to build," it enters a prioritized queue.
The rig picks it up automatically, breaks it into GitHub issues, and processes them through the pipeline. When all issues for a feature are done, the status updates back in Asana/ClickUp.
Engineering work (technical debt, bugs, infrastructure) lives in GitHub and never needs to leave.
Issue Dependencies¶
GitHub has native issue relationships: blocked by and blocking. The Conductor reads these directly from the GitHub API to determine execution order.
Milestones are for grouping and progress tracking only. All ordering logic is issue-to-issue.
If an agent fails on an issue, all downstream issues stay blocked automatically.
Labels¶
Labels are independent states, not a lifecycle. An issue can have multiple labels at the same time.
| Label | Who sets it | Meaning |
|---|---|---|
approved |
CTO | Approved to be worked on |
blocked |
Conductor | Has unresolved dependencies |
in-progress |
Conductor | Agent is working on it |
in-review |
Conductor | PR open |
done |
Conductor | Merged and deployed |
critical |
CTO / Agent | Drop everything |
high-priority |
CTO | Do before normal work |
An issue can be approved + blocked at the same time. The Conductor only assigns issues that are approved AND NOT blocked.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A["approved + blocked"] -->|"blockers done"| B["approved"]
B -->|"agent picks up"| C["approved + in-progress"]
C -->|"PR opened"| D["approved + in-review"]
D -->|"merged + deployed"| E["done"]
style A fill:#f87171,color:#000
style B fill:#34d399,color:#000
style C fill:#60a5fa,color:#000
style D fill:#a78bfa,color:#000
style E fill:#94a3b8,color:#000
The CTO labels all issues as approved when planning is complete. The Conductor adds blocked to issues with unresolved dependencies and removes it when blockers are done.
The Conductor¶
How the Conductor takes work from "approved" to "deployed in production."
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
START[Find approved + not blocked] --> ASSIGN[Assign to agent]
ASSIGN --> PIPELINE[Agent · Build · Test · Review · Deploy]
PIPELINE --> DONE[Done]
DONE --> CHECK{More issues?}
CHECK -->|yes| START
CHECK -->|no| COMPLETE[Notify]
style START fill:#34d399,color:#000
style ASSIGN fill:#a78bfa,color:#000
style PIPELINE fill:#a78bfa,color:#000
style DONE fill:#34d399,color:#000
style CHECK fill:#fbbf24,color:#000
style COMPLETE fill:#e9a820,color:#000
Example: AI Chat Widget¶
Four issues in one milestone. The Conductor works them in dependency order.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A["#1 LLM tool API (backend)"] --> C["#3 Connect UI to API"]
B["#2 Chat UI component"] --> C
C --> D["#4 Analytics tracking"]
style A fill:#34d399,color:#000
style B fill:#34d399,color:#000
style C fill:#f87171,color:#000
style D fill:#f87171,color:#000
All four issues are approved by the CTO. #3 and #4 are also blocked (red).
Step 1: #1 and #2 are approved + not blocked (green). Conductor assigns both to available agents. They run in parallel.
Step 2: #1 and #2 done. Conductor removes blocked from #3. Assigns #3.
Step 3: #3 done. Conductor removes blocked from #4. Assigns #4.
Step 4: All issues done. Milestone complete. Notify Slack + update Asana/ClickUp.
How the Conductor drives this¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
subgraph Round1 ["Round 1: parallel"]
A1["#1 LLM tool API → Agent 1"]
A2["#2 Chat UI → Agent 2"]
end
subgraph Round2 ["Round 2: #3 unblocked"]
A3["#3 Connect UI to API → Agent 1"]
end
subgraph Round3 ["Round 3: #4 unblocked"]
A4["#4 Analytics → Agent 1"]
end
CTO["CTO approves all 4 issues"] --> Round1
Round1 -->|"both deployed"| COND1["Conductor removes blocked from #3"]
COND1 --> Round2
Round2 -->|"deployed"| COND2["Conductor removes blocked from #4"]
COND2 --> Round3
Round3 -->|"deployed"| DONE["Milestone complete → Slack + Asana/ClickUp"]
style CTO fill:#38bdf8,color:#000
style A1 fill:#a78bfa,color:#000
style A2 fill:#a78bfa,color:#000
style A3 fill:#a78bfa,color:#000
style A4 fill:#a78bfa,color:#000
style COND1 fill:#34d399,color:#000
style COND2 fill:#34d399,color:#000
style DONE fill:#e9a820,color:#000
Priority interrupts¶
A high-priority issue (e.g., production bug) jumps the queue.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
R1["Round 1: #1 and #2 in progress"] --> BUG["🚨 Critical bug reported"]
BUG --> COND["Conductor: assign bug to next available agent"]
COND --> FIX["Agent fixes bug → deployed"]
FIX --> RESUME["Resume: #1 and #2 continue"]
RESUME --> R2["Round 2: milestone continues"]
style BUG fill:#f87171,color:#000
style COND fill:#34d399,color:#000
style FIX fill:#a78bfa,color:#000
style RESUME fill:#34d399,color:#000
The milestone work pauses for the agent handling the bug, then resumes. Other agents keep working on their current issue.
Priority queue¶
When multiple issues are approved, the Conductor picks work in this order:
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
Q["All approved + not blocked issues"] --> P1
P1["1. Critical"] --> P2["2. High priority"]
P2 --> P3["3. Normal priority"]
P3 --> TIE["Same priority? Oldest approved first"]
TIE --> ASSIGN["Assign to next available agent"]
style Q fill:#60a5fa,color:#000
style P1 fill:#f87171,color:#000
style P2 fill:#fbbf24,color:#000
style P3 fill:#34d399,color:#000
style TIE fill:#94a3b8,color:#000
style ASSIGN fill:#a78bfa,color:#000
All approved, unblocked issues from all milestones and standalone issues go into one queue. The Conductor always picks the highest priority, oldest approved issue for the next available agent.
Conductor logic¶
The entire Conductor is one loop, running every 60 seconds. Stateless. If it crashes, restart it and nothing is lost.
every 60 seconds:
# Check all approved issues and update blocked status
for issue in github.get_issues(label: "approved"):
blockers = github.get_blocking_relationships(issue)
has_unresolved = any(b.state == "open" for b in blockers)
if has_unresolved and "blocked" not in issue.labels:
github.add_label(issue, "blocked")
if not has_unresolved and "blocked" in issue.labels:
github.remove_label(issue, "blocked")
# Find assignable issues: approved AND NOT blocked AND NOT in-progress/in-review/done
assignable = github.get_issues(label: "approved", exclude_labels: ["blocked", "in-progress", "in-review", "done"])
# Sort: priority first, then oldest approved
assignable.sort(key: (priority_rank(issue.labels), issue.approved_timestamp))
# Assign to available agents
for agent in agents.get_idle():
if assignable is empty: break
issue = assignable.pop_first()
github.add_label(issue, "in-progress")
github.assign(issue, agent)
slack.notify(f"{agent} started {issue.title}")
# Check completed issues and milestone progress
for issue in github.get_issues(label: "done", since: last_check):
if issue.milestone:
milestone_issues = github.get_issues(milestone: issue.milestone)
if all(i.state == "closed" for i in milestone_issues):
github.close_milestone(issue.milestone)
asana.update_status(issue.milestone, "complete")
slack.notify(f"Milestone complete: {issue.milestone.title}")
def priority_rank(labels):
if "critical" in labels: return 0
if "high-priority" in labels: return 1
return 2
Inside the Automated Engineering Rig¶
Three stages from issue to production.
1. Agents¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A[GitHub Issue] --> B[Agent claims]
B --> C[Create branch]
C --> D[Implement]
D --> E[Open PR]
style A fill:#60a5fa,color:#000
style B fill:#a78bfa,color:#000
style D fill:#a78bfa,color:#000
style E fill:#a78bfa,color:#000
An agent picks up the next issue, creates a branch, implements the change, and opens a pull request. Fully automated.
2. Build, Test, Review¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A[PR] --> B[Build + Lint]
B --> C[Tests]
C --> D{Pass?}
D -->|no| E[Agent fixes]
E --> B
D -->|yes| F[Code Review]
F --> G{Human gate?}
G -->|no| H[Auto-merge]
G -->|yes| I[Human review]
I --> H
style A fill:#a78bfa,color:#000
style B fill:#a78bfa,color:#000
style C fill:#a78bfa,color:#000
style D fill:#fbbf24,color:#000
style E fill:#a78bfa,color:#000
style F fill:#a78bfa,color:#000
style G fill:#fbbf24,color:#000
style I fill:#f59e0b,color:#000
style H fill:#34d399,color:#000
CI runs automatically. If tests fail, the agent fixes and retries. Code review is automated unless a human gate is triggered (auth, payments, schema, new dependencies).
3. Deploy¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A[Merge to main] --> B[Staging]
B --> C[Smoke tests]
C --> D[Release PR]
D --> E[Human merges]
E --> F[Production]
style A fill:#34d399,color:#000
style B fill:#34d399,color:#000
style D fill:#34d399,color:#000
style E fill:#f59e0b,color:#000
style F fill:#34d399,color:#000
Merging to main auto-deploys to staging. Production deploy is always a human action.
Color key: purple = automated, yellow = decision, orange = human, green = progression.
Human Gates¶
Some changes always require a human:
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
PR[Pull Request] --> CHECK{What changed?}
CHECK -->|Auth or session code| HUMAN[Human review required]
CHECK -->|Payment code| HUMAN
CHECK -->|Database schema| HUMAN
CHECK -->|Data deletion logic| HUMAN
CHECK -->|New dependency| HUMAN
CHECK -->|Everything else| AUTO[Automated review]
AUTO --> MERGE[Merge]
HUMAN --> MERGE
style HUMAN fill:#f87171,color:#000
style AUTO fill:#34d399,color:#000
Enforced via CODEOWNERS and label-gated CI checks, not by convention.
Phases¶
Phase 1: Before anything is live¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
subgraph In Place
A[Git + branch protection]
B[CI on every PR]
C[Conventional commits]
D[Staging auto-deploy on merge]
end
subgraph Not Yet
E[Production environment]
F[Dev agent]
G[Usage analytics]
end
style A fill:#34d399,color:#000
style B fill:#34d399,color:#000
style C fill:#34d399,color:#000
style D fill:#34d399,color:#000
style E fill:#cbd5e1,color:#000
style F fill:#cbd5e1,color:#000
style G fill:#cbd5e1,color:#000
Work is driven by humans. The pipeline handles CI, testing, and deploys.
Phase 2: Going live¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
subgraph Added
A[Production environment]
B[Release pipeline]
C[Monitoring + alerting]
D[Error tracking]
E[Database backups]
end
subgraph Deploy Flow
F[Merge to main] --> G[Auto-deploy to staging]
G --> H[Smoke tests]
H --> I[Release PR opened automatically]
I --> J[Human merges = production deploy]
J --> K[Post-deploy smoke tests]
end
style A fill:#34d399,color:#000
style J fill:#fbbf24,color:#000
Phase 3: Dev agent¶
A Kubernetes-based agent joins the team. It works like any other developer, through the same pipeline.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
subgraph Coordinator
A[Scan for approved issues] --> B{Agent available?}
B -->|yes| C[Spawn agent pod]
B -->|no| A
end
subgraph Agent Pod
C --> D[Claim issue]
D --> E[Clone repo]
E --> F[Implement change]
F --> G[Create PR]
G --> H[Pod terminates]
end
subgraph Same Pipeline
G --> I[CI runs]
I --> J[Code review]
J --> K{Human gate?}
K -->|no| L[Auto-merge]
K -->|yes| M[Human review]
M --> L
end
style C fill:#60a5fa,color:#000
style G fill:#a78bfa,color:#000
style K fill:#fbbf24,color:#000
The agent is ephemeral: one pod per task, destroyed after the PR is created. No persistent state needed.
What the agent can do:
- Implement features and fixes from well-defined issues
- Write and run tests
- Create pull requests
- Respond to review comments
What the agent cannot do:
- Merge to production
- Change auth, payment, or data deletion code without human review
- Modify database schemas without approval
- Install new dependencies without review
Phase 4: Flywheel¶
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph TD
A[Human feedback] --> C[Product decisions]
B[Usage data] --> C
C --> D[Specs + issues]
D --> E[Dev agent + humans]
E --> F[Pipeline]
F --> G[Production]
G --> B
style C fill:#f87171,color:#000
style E fill:#60a5fa,color:#000
style G fill:#34d399,color:#000
The pipeline is the same at every phase. What changes is who feeds work into it and how much of the execution is automated.
Role Separation¶
The agent that writes code does not review it.
%%{init: {"theme": "base", "themeVariables": {"primaryColor": "#e2e8f0", "primaryTextColor": "#1e293b", "primaryBorderColor": "#64748b", "lineColor": "#94a3b8", "secondaryColor": "#e2e8f0", "tertiaryColor": "#cbd5e1", "background": "#0f172a", "mainBkg": "#e2e8f0", "nodeBorder": "#64748b", "clusterBkg": "#1e293b", "clusterBorder": "#475569", "titleColor": "#e2e8f0", "edgeLabelBackground": "#1e293b", "nodeTextColor": "#1e293b"}}}%%
graph LR
A[Issue] --> B[Implementation Agent]
B --> C[Pull Request]
C --> D[Separate Review]
D --> E[Human if flagged]
E --> F[Merge]
style B fill:#60a5fa,color:#000
style D fill:#a78bfa,color:#000
style E fill:#fbbf24,color:#000
This is structural. An AI reviewing its own output will find it acceptable more often than it should.