Skip to content

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.