Shahzad Bhatti Welcome to my ramblings and rants!

August 25, 2026

Orchestrating Background AI Agents for Software Teams

Filed under: Computing — admin @ 4:25 pm

How to design agents as a graph, run them through a harness that keeps them reliable, sandbox them because you can’t just trust what they do. (This is a follow-up to AI Writes Code, You Own the Design and Declarative AI Coding Agents with an Orchestration System)


I have been using various workflow systems for business process management, data pipelines and batch processing over twenty years. I built a declarative orchestration system over ten years, which was before GitHub Actions, CircleCI, and GitLab CI. The idea was simple: describe your work as a graph of tasks with pipes and filter patterns declaratively, give each task clear inputs and outputs, let a server run them on a schedule or in response to events, and report the results back. Automation has always been the core discipline of software engineering and we built tools for CI/CD, cron jobs, and data pipelines. With AI, your job is changing from the one who executes the steps to a conductor. Instead of manual coding, testing and other tasks, you now design the graph of nodes, define what each node is allowed to do, let agents work on the nodes while you monitor the work where it needs a human hand. You need a harness around each agent so the graph runs reliably, a sandbox for every agent because you fundamentally cannot trust what an LLM decides to do, and a skills layer so the same harness stays general-purpose.

I have seen teams building tightly coupled monolithic agentic systems that includes poorly implemented orchestration, the integrations, the prompts, and the skills, all bolted together. This makes it harder to make changes or extend agent capabilities and skills independently. In this post I will walk through three open source projects: Formicary, the orchestration engine that turns workflows into a graph with the Slack interface; ai-dev-tools, the harness of small scripts that actually do the work; and you-got-skills, the library of skills for SDLC.


Daily Routine

Each morning, you typically have to scan Jira/Github board, check pull requests to review, triage any new blockers, scroll through Slack messages to respond before deciding what to actually work on. AI coding tools have automated the implementation but you still need to know what the team is doing, catching problems, following up on reviews, etc. The basic flaw in most “agentic” setups today is that they’re still fundamentally interactive. You close your laptop and the agent stops. You need a way to keep agents working in background and instead of building systems that make you go pull information, and build agents that push it to you instead. You need somewhere to define the graph of who-triggers-what in a sandbox environment. You need a harness around each step so a flaky script doesn’t take the whole pipeline down. And you need the agent’s behavior to live somewhere editable, not buried in a prompt string, so the system stays general enough to point at a new kind of problem without a rewrite.


The Architecture: Three Tools, Four Layers

This section describes three open source tools: Formicary is the orchestration engine with Slack integration; ai-dev-tools is the harness that actually runs each agent inside a sandbox; and you-got-skills is the knowledge layer that keeps the whole system general-purpose.

Above diagram shows a graph where every box is a node with a job type, a task, a skill and every arrow is an edge that Formicary evaluates at runtime. Designing agents this way, instead of as one long prompt with a loop around it makes the system debuggable. The ai-dev-tools is the harness that runs inside each node and it turns “call an LLM” into “call an LLM inside a container, with a defined timeout, a defined exit-code contract in a sandbox environment. You should not trust an agent’s judgment about what’s safe to run but with sandbox you let the harness enforce the boundary.

Why hand off state through files?

Formicary provides declarative syntax to store artifacts or consume artifacts from a previous task. Every script starts by checking whether its own output already exists:

# Every script starts with this pattern
existing = read_json(config, issue_id, "plan_result.json")
if existing and existing.get("status") == "DONE":
    print("Already done, skipping")
    sys.exit(0)

If a task dies halfway through and gets re-run, it just picks up where it left off. Exit codes are the contract between a script and the orchestrator:

Exit codeMeaningWhat Formicary does
0SuccessMove on to the next task
1Error (worth retrying)Retry with backoff
2Blocked for a human inputPause the job indefinitely
3Not finished yet / waiting on somethingPause, then resume on a trigger or after a delay

The Skills Library

Skills are the knowledge layer that allow general-purpose harness instead of hardcoded to whatever workflow you built. The graph and the sandbox don’t know anything about code review or standups as they just run nodes. A skill is what tells an agent how to do a specific kind of engineering work well. Add a new skill and you’ve extended the system to a new kind of task without touching Formicary or ai-dev-tools at all.

you-got-skills/
??? skills/
    ??? ygs-standup/skill.md
    ??? ygs-risk-scan/skill.md
    ??? ygs-implement/skill.md
    ??? ygs-review-pr/skill.md
    ??? ygs-code-review/skill.md
    ??? ygs-security-review/skill.md
    ??? ygs-sre-review/skill.md
    ??? ygs-learn/skill.md
    ??? ygs-retro/skill.md
    ??? ygs-sprint-plan/skill.md
    ??? ygs-investigate/skill.md
    ??? ygs-qa/skill.md
    ??? ygs-wbs/skill.md
    ??? ygs-estimate/skill.md
    ??? ygs-ship/skill.md
    ??? ygs-triage/skill.md
    ??? ... 28 total

Together they cover the whole lifecycle:

PhaseSkills
Requirementsygs-refine-prd, ygs-review-prd, ygs-refine-trd, ygs-review-trd
Architectureygs-refine-architecture, ygs-review-architecture, ygs-spike
Planningygs-sprint-plan, ygs-wbs, ygs-estimate, ygs-triage
Executionygs-implement, ygs-qa, ygs-ship, ygs-uat
Reviewygs-review-pr, ygs-code-review, ygs-security-review, ygs-sre-review, ygs-api-review, ygs-ui-review
Team intelligenceygs-standup, ygs-risk-scan, ygs-pr-queue, ygs-sync
Learningygs-learn, ygs-retro, ygs-investigate

Skills produce and consume a consistent folder structure inside your project:

your-project/
??? docs/
?   ??? prd/              # Product requirements (YYYY-MM-DD-slug.md)
?   ??? trd/               # Technical designs
?   ??? adr/               # Architecture decisions (NNN-slug.md)
?   ??? spikes/            # Spike findings
?   ??? learnings/         # Learnings pulled from PRs and incidents
??? tasks/
    ??? backlog/            # task-NNN.md
    ??? in-progress/        # moving a file here = starting it
    ??? done/               # moving a file here = finishing it

Status is the folder. No ticket-state dropdown, no transition workflow to configure. mv tasks/backlog/task-042.md tasks/in-progress/ means the task has started. git blame tells you who moved it and when.

Diverge, then converge

The diverage and converge pattern allows running multiple independent LLM passes first (diverge), then merge and rank what came back (converge). A single reviewer may miss something but several independent reviewers will catch most of the issues. The ygs-review-pr skill runs four independent passes in parallel for correctness, security, API surface, and SRE concerns. A finalize step then merges everything and ranks it by severity.

The standup workflow uses the same idea. ygs-standup gathers signals from the issue tracker and from Slack then cross-references the two.


The Standup Workflow

Every weekday at 8am, a cron job wakes up, queries your Jira sprint (or GitHub), reads the open PRs, pulls the last 26 hours of Slack messages from your standup channel, and turns all of it into a brief.

# ai-standup-jira.yaml
job_type: ai-standup-jira
description: "Daily standup brief from Jira sprint + Bitbucket PRs + Slack signals"
cron_trigger: "0 0 8 * * 1-5 *"
max_concurrency: 1
timeout: 900s

tasks:
  - task_type: gather     # pulls Jira issues, Bitbucket PRs, Slack in parallel
  - task_type: synthesize # Claude + ygs-standup + ygs-risk-scan ? standup_brief.md
  - task_type: post       # renders HTML artifact, posts to Slack

The synthesize task calls the ygs-standup skill, which follows a fairly strict protocol:

**Alice:** Closed PROJ-42 (auth fix). Working on PROJ-51 (rate limiter)
— PR open 28h, no review yet. [Slack: "waiting on infra cert renewal"]

**Bob:** No tracker activity in the last 24h. Last Slack message Monday
(3 days ago). [Slack: silent since Monday]

**Carol:** PROJ-55 (data export) marked In Progress, no commits in 4 days.
Blocked label present. [Tracker: blocked since Tuesday]

Every claim traces back to a ticket, a PR, or a Slack message. ygs-risk-scan runs right after and appends a ranked list, using thresholds you can tune:

? HIGH  PROJ-55 blocked, blocks PROJ-60 (in progress, owned by different person)
? MED   PR #142 open 28h, single reviewer, no activity
? MED   Carol: no updates in 4 days, sprint ends Friday

The thresholds live in a shared Markdown file:

SignalDefault severityEscalates to HIGH if…
Issue stale > 3 daysMEDIUMit blocks another issue
Issue stale > 5 daysHIGH
PR open > 2 days, no reviewMEDIUMonly one reviewer assigned
PR open > 4 daysHIGH
Person silent > 2 daysMEDIUMalso no tracker activity
Blocked labelHIGH
Dependency chain: upstream is staleHIGH
Sprint ends in < 2 days, not startedHIGH

You can also trigger the standup on demand from Slack:

@bot standup
@bot risk

Label an Issue, Get Back a Pull Request

You can label any Jira or GitHub issue ai-ready. Every five minutes a cron job checks for newly labeled issues and kicks off a four-task pipeline. When it’s done, there’s an open PR with an implementation and tests, the label has flipped to ai-pr-open.

# ai-gh-issue-picker.yaml
job_type: ai-gh-issue-picker
cron_trigger: "*/5 * * * *"
tasks:
  - task_type: gather-issues
    script:
      - python -m scripts.gh.issue_picker
    on_exit_code:
      2: COMPLETED  # no issues = not an error
  - task_type: submit-jobs
    # Uses formicary template to fan out one ai-gh-implement job per issue
    script:
      - '{{SubmitJobsFromJSON "ai-gh-implement" .IssuesJSON}}'

It has a built-in guard: if 10 or more implement jobs are already running or queued, it skips its turn. That stops someone from labeling 50 issues at once and blowing up the queue. The implementation pipeline itself:

# ai-gh-implement.yaml
job_type: ai-gh-implement
max_concurrency: 5
timeout: 86400s    # 24 hours — some implementations take a while

tasks:
  - task_type: plan
    timeout: 15m
    script:
      - python -m scripts.gh.issue_picker --issue-id {{.IssueNumber}}
      - python -m scripts.gh.plan --issue-id {{.IssueNumber}}
    on_exit_code:
      2: PAUSE_JOB    # BLOCKED — needs a human before continuing
    on_completed: implement

  - task_type: implement
    timeout: 45m
    script:
      - python -m scripts.gh.implement --issue-id {{.IssueNumber}}
    on_completed: create-pr

  - task_type: self-review
    timeout: 10m
    script:
      - python -m scripts.review.run --mode self-review
          --issue-id {{.IssueNumber}} --base-branch main
    on_exit_code:
      2: PAUSE_JOB    # BLOCKED — critical finding, needs a human before the PR opens
    on_completed: create-pr

  - task_type: create-pr
    timeout: 10m
    script:
      - python -m scripts.gh.create_pr --issue-id {{.IssueNumber}}
    on_completed: poll-pr

  - task_type: poll-pr
    dependencies: [create-pr, poll-pr]  # self-referencing = loop
    script:
      - python -m scripts.gh.poll_pr --issue-id {{.IssueNumber}}
    on_exit_code:
      3: PAUSE_JOB    # PR still open — check back in `delay` seconds
    delay: "{{.PollInterval}}s"  # default 120s

Here’s the full chain:

Let’s walk through what actually happens for a real issue.

Plan

scripts/gh/plan.py calls Claude with up to 50 turns and a prompt that tells it to:

1. Read CLAUDE.md, .cursorrules, or any repo-specific coding guidelines if they exist
2. Discover .claude/skills/ in the repo — if a skill applies, plan to invoke it
3. Before designing new abstractions, search utils/, shared/, common/ for existing utilities
4. Check for monorepo structure
5. Generate a concise plan covering:
   - Task breakdown with complexity estimates (S/M/H/XL)
   - Exact files to create/modify per task
   - Test strategy: write failing tests first, then implement
   - A "Failing Test Spec" section
   - Any risks or blockers
6. Classify overall complexity: S/low (?3 files), M/medium (4-10), H/high (>10)
7. Write the plan to PLANS/{slug}-{issue_id}-plan.md

What it produces:

/workspace/42/
??? issue.json         # issue title, body, labels, assignee
??? plan.md            # human-readable plan
??? plan_result.json   # {"status":"DONE","task_count":3,"total_complexity":"M"}
??? PLANS/
    ??? add-rate-limit-42-plan.md

Implement

scripts/gh/implement.py calls Claude with up to 200 turns. The key rules from the ygs-implement skill:

  • Check for existing utilities before writing new ones.
  • TDD: write the failing test first, then make it pass.
  • One commit per plan task, message format "task: <description>".
  • After all tasks are done, run the full test suite and iterate on failures up to twice.
/workspace/42/
??? impl_result.json   # {"status":"DONE","files_changed":["src/..."],"commits":5,"tests_status":"passing"}
??? branch.txt         # "ai/42-add-rate-limiting"

ygs-implement also uses “ceremony levels” so small tasks don’t get over-engineered:

Light    (1-3 files, <300 lines):   proceed directly, skip checkpoints
Standard (4-8 files, 300-800):      plan mode + checkpoint every 5 files
Heavy    (8+ files, 800+ lines):    flag as oversized, ask user to split

Picking the model by complexity

The plan task classifies overall complexity and writes it to /workspace/plan_complexity.txt. The implement task reads that and picks the right model:

# scripts/common/config.py
COMPLEXITY_MODEL_MAP = {
    "low":    MODEL_BEDROCK_HAIKU,   # ?3 files, simple edits — fast and cheap
    "medium": MODEL_BEDROCK_SONNET,  # default — most issues
    "high":   MODEL_BEDROCK_OPUS,    # complex architecture changes
}

The plan prompt writes a single word (low, medium, or high) to that file, and the implement task reads it back:

# In ai-gh-implement.yaml — implement task
COMPLEXITY=$(cat /workspace/plan_complexity.txt 2>/dev/null || echo "medium")
AI_MODEL="${ANTHROPIC_COMPLEXITY_${COMPLEXITY^^}_MODEL:-${ANTHROPIC_DEFAULT_SONNET_MODEL}}"

AnthropicComplexityLowModel and AnthropicComplexityHighModel are set in your org config by deploy-ai-workflows.sh, and can be overridden per deployment in models.env.

Polling the PR and responding to feedback

Every 2 minutes, the poll task checks the PR for new comments. It only reacts to comments that start with ai-bot. The agent stays out of human-to-human review discussion, and it never responds to its own earlier comments. So when a reviewer writes:

ai-bot please add a test for the rate limit exceeded case

The poll task reads it, applies the feedback, marks the comment handled in processed_comments.json, and keeps polling. Once the PR merges, the learning step kicks off automatically.


PR Review With a Human Gate

Code review usesthe diverge-then-converge pattern to review the code with multiple perspectives like security, architecture, SRE, etc.

# ai-gh-review.yaml
job_type: ai-gh-review
max_concurrency: 10

tasks:
  - task_type: review        # Claude runs ygs-review-pr, writes findings.json
  - task_type: await-feedback # posts Block Kit to Slack, exits 3 ? PAUSE_JOB
  - task_type: finalize       # reads Decision, posts result to PR thread

The review step runs ygs-review-pr with this instruction:

1. Invoke the /ygs-review-pr skill to perform a full PR review
2. After the skill completes, write findings to findings.json
3. Output ONLY this JSON on the last line:
   {"status":"DONE","findings_count":N,"verdict":"APPROVE|REQUEST_CHANGES","summary":"..."}

ygs-review-pr runs four passes in paralle:

  1. Correctness: logic errors, null handling, incomplete enum handling, partial failure, race conditions, off-by-one errors
  2. Security: injection vectors (SQL, command, XSS, SSRF, path traversal), auth/authorization, data exposure across tenant boundaries, etc.
  3. API surface: breaking changes, contract violations, backwards compatibility, versioning
  4. SRE: failure modes, blast radius, observability gaps, rollback safety, resource consumption, dependency risk

Once all four are done, findings get merged and ranked: CRITICAL > HIGH > MEDIUM > LOW, and by confidence within each level. The verdict maps straight off the highest severity found:

Any CRITICAL or HIGH finding  ? REQUEST_CHANGES
Only MEDIUM/LOW findings      ? COMMENT
No findings / only low conf.  ? APPROVE

Deep review: seven domains in one pass

Standard review runs four passes. Deep review adds three more: performance, testing quality, and architecture.

@bot deep review https://github.com/org/repo/pull/42

These all map to the same ai-gh-review (or ai-jira-review) job type, just with ReviewDepth=deep injected as a static job variable:

# workflows.yml — deep-review entry
- name: deep-review
  job_type: ai-gh-review
  triggers: ["deep review", "full review", "arch review"]
  target_kind: github
  extra_params:
    ReviewDepth: "deep"
  description: "Deep 7-domain GitHub PR review: standard + performance, testing quality, architecture"

The workflow reads ReviewDepth and picks the right skill: ygs-review-deep when it’s set to “deep,” ygs-review-pr otherwise. ygs-review-deep is a superset of the standard four passes, plus:

  • Performance: algorithmic complexity, N+1 queries, cache misses, lock contention, unnecessary allocations
  • Testing quality: coverage gaps, brittle assertions, missing edge cases, test-code coupling
  • Architecture: single-responsibility violations, circular dependencies, premature abstractions, missing boundaries

Self-review before the PR even opens

The implement pipeline runs a self-review task right before create-pr. Before the PR exists, the agent reviews its own diff against the base branch:

  - task_type: self-review
    timeout: 10m
    script:
      - python -m scripts.review.run --mode self-review
          --issue-id {{.IssueNumber}} --base-branch {{.BaseBranch}}
    on_exit_code:
      2: PAUSE_JOB    # BLOCKED — critical finding, needs a human before the PR opens
    on_completed: create-pr

This runs ygs-implement in review mode, compares the diff to the original plan, and writes self_review.json. The outcome maps directly to an exit code:

self_review_statusExit codeWhat the pipeline does
APPROVED0Proceed to create-pr
NEEDS_FIX0Claude fixes it inline, then create-pr
BLOCKED2PAUSE_JOB — a human needs to decide before the PR opens

How Slack Messages Turn Into Workflows

Socket Mode lives inside the Formicary queen itself. The queen opens an outbound WebSocket to Slack using your xapp- app-level token. When you mention the bot:

@bot review https://github.com/myorg/myrepo/pull/142

The queen’s SlackService does one thing, deterministically: it strips the mention, takes the first word, and looks it up against a route table in the queen’s config.

# In k8s/formicary-leader.yaml ConfigMap, under slack.routes:
slack:
  routes:
    - triggers: ["review", "pr"]
      job_type: ai-gh-review
      description: "PR review: correctness, security, API, SRE"
    - triggers: ["implement", "build"]
      job_type: ai-jira-implement
      description: "Full pipeline: plan ? implement ? PR"
    - triggers: ["standup", "status", "daily"]
      job_type: ai-standup-jira
      description: "Daily standup brief from Jira"
    - triggers: ["adhoc"]
      job_type: ai-adhoc
      description: "Ad-hoc Claude invocation with any skill"

Everything after the trigger word gets passed through as the Prompt job parameter, verbatim. The queen’s whole job is mapping verb to job type and passing the text along. It does zero AI work of its own. All of that happens inside the ai-dev-tools container once the job actually starts. Once routing resolves, the queen submits the job and replies right in the thread:

Started ai-gh-review (job req-7f3a2) — I'll post updates here.
https://formicary.example.com/dashboard/jobs/requests/req-7f3a2

Replying to the thread resumes a paused job. If a review job is paused waiting on a decision and you reply in that thread, the queen matches it by SlackThreadTs and resumes it with your reply text injected as Prompt.

Registering as a developer

Before Slack commands work for you, you DM the bot your Formicary API token, once:

DM to @bot:
setup eyJhbGc...  (your Formicary API token)

The queen validates the token inline and from that point on, any @bot mention from you has a known Formicary identity behind it.

How multi-tenant isolation actually works, end to end:

When you type @bot review https://github.com/org/repo/pull/42, here’s what the queen does, entirely server-side:

  1. Reads your Slack user ID (U0A1HQL0C9J) off the Socket Mode event.
  2. Looks up slack_user_id = U0A1HQL0C9J in user_configs and finds your Formicary user record, including your UserID and OrganizationID.
  3. Calls SaveJobRequest(qc, req) and the server overwrites request.UserID and request.OrganizationID from that context.
  4. Schedules the job and the ant scheduler first looks for a worker registered under your org_id.

Ant routing: when you connect your laptop as a worker with setup-ant-worker.sh --token <your-token>, the queen reads org_id out of your JWT at connect time and records it on that worker. Your jobs prefer your own worker.

All the commands

What you typeWhat runs
@bot standupDaily brief: per-person status, risks, discussion questions (routes to Jira or GitHub via DEFAULT_TRACKER)
@bot risk / @bot risksRanked sprint risks with a capacity check
@bot prs / @bot open prs / @bot review queueOpen PRs grouped by reviewer status, sorted by age
@bot pr comments <url>All inline feedback and open tasks for a PR
@bot review <github-url>Standard PR review: correctness, security, API, SRE (4 domains)
@bot review <bitbucket-url>Same, for Bitbucket PRs
@bot deep review <url>Deep review: standard 4 domains + performance, testing, architecture (7 domains)
@bot full review <url>Alias for deep review
@bot arch review <url>Alias for deep review
@bot security review <url>OWASP-focused security audit
@bot sre review <url>Failure modes, observability, deploy safety
@bot implement PROJ-123Full pipeline: plan ? implement ? self-review ? PR, for a Jira issue
@bot implement 42Same, for a GitHub issue number (model picked by complexity: Haiku/Sonnet/Opus)
@bot jira-query <term> / @bot qjira <term>Search open Jira issues by keyword, results as a Block Kit table
@bot jira-analyze PROJ-1, PROJ-2Claude analyzes root cause + possible fixes for Jira issues
@bot gh-query <term>Search open GitHub issues by keyword
@bot gh-analyze #123, #456Claude analyzes root cause + possible fixes for GitHub issues
@bot adhoc <free text>Run any Claude skill with a freeform prompt
@bot helpList every command

Ad-hoc Skill Execution

ai-adhoc is a general-purpose runner: any you-got-skills skill can be invoked with a free-form prompt, and the result comes back into your Slack thread.

# ai-adhoc.yaml
job_type: ai-adhoc
max_concurrency: 20
timeout: 1800s

variables:
  Skill:  { type: STRING, required: true }
  Prompt: { type: STRING, required: true }

At runtime the script:

  1. Looks for the skill in a few candidate locations (/workspace/skills, ~/.claude/skills/you-got-skills/skills, ~/workplace/you-got-skills/skills).
  2. Writes .ygs/tracker.yml dynamically from environment variables (Jira or GitHub config, team members, sprint info).
  3. Invokes Claude with the skill content and your prompt.
  4. Strips Markdown formatting from the output so it renders cleanly in Slack.
  5. Posts up to 3000 characters back into the originating thread.

Adding a new shorthand command is just one entry in the queen’s route table:

# k8s/formicary-leader.yaml — slack.routes
slack:
  routes:
    - triggers: ["prs", "open prs", "review queue"]
      job_type: ai-adhoc
      description: "Open PRs grouped by review status"

@bot prs then submits ai-adhoc with Prompt="prs", and the container maps that to the ygs-pr-queue skill. No Python code changes needed.


Querying and Analyzing Issues From Slack

Two commands take you from a Slack message straight to structured Jira insight, no browser required.

@bot query-jira: find issues by keyword

@bot jira-query auth timeout

This submits an ai-jira-query job. The script builds a JQL query scoped to your project and, optionally, your team’s custom field. Results come back as a structured Slack Block Kit table:

Jira issues matching "flaky tests" (5 found)

PROJ-1001  [Bug] Flaky test in auth service                    - clickable link
           Status: In Progress   Priority: High
           Assignee: alice        Date: 2026-07-28

PROJ-995   [Story] Fix race condition in logger test
           Status: To Do         Priority: Medium
           Assignee: bob         Date: 2026-07-21
...

@bot jira-analyze: root cause from issue keys

@bot jira-analyze https://yourorg.atlassian.net/browse/PROJ-1001

This routes to the same ai-jira-query job type. The result posts back to your thread.

@bot gh-query / @bot gh-analyze the GitHub equivalents

Same commands, gh- prefix, for teams on GitHub instead of Jira:

@bot gh-query open authentication bugs
@bot gh-analyze https://github.com/org/repo/issues/42

Team filtering

Both commands automatically filter to your configured team and sprint:

  • JIRA_SPACE (or BITBUCKET_WORKSPACE): the team/area filter value.
  • JIRA_TEAM_FIELD: the Jira custom field name (default EngScrumTeam, resolved to a field ID dynamically).
  • Set JIRA_TEAM_FIELD="" to turn the filter off entirely.

The Learning Loop: Getting Better Over Time

Most agent systems are stateless and every run starts from zero. In our workflow, after every PR merges, learn.py runs automatically as part of the poll-pr task. It reads the PR comments, the implementation artifacts, and the review findings, then invokes ygs-learn:

ygs-learn protocol:
1. Capture: What happened? Why does it matter? Category?
2. Dedup: search docs/learnings/ for similar slug before creating new
3. Write to docs/learnings/YYYY-MM-DD-slug.md

A learning document looks like this:

# Rate limiter key collision when user has multiple active sessions

**Category:** Edge Case
**Date:** 2025-08-03
**Source:** PR review finding, PROJ-123

## Learning
When a user has multiple active sessions, rate limiting by user_id counts across
all sessions. A single slow client can exhaust the budget for all their tabs.

## Evidence
Review comment on PR #142 flagged this. Reproduced locally with two
concurrent sessions against the same account.

## Application
When implementing per-user rate limits, check whether session isolation is
intended. If counts should be per-session, key by session_id not user_id.

Next time an implementation runs, that document is part of the context Claude reads.

ygs-retro runs at sprint end. It reads tasks/done/, recent git history, and the sprint’s accumulated learnings, and asks pointed questions based on what it actually found.

ygs-investigate enforces a debugging discipline: build a feedback loop first, e.g., a failing test, a log line, a REPL session before forming any hypotheses. Rank hypotheses 1 through 5. Instrument one variable at a time with tagged markers.


Trust and Oversight

Though, AI agents have solved most of coding and testing tasks but it still requires human review and feedback. We need a trusted autonomy, with minimal friction. You don’t trust the model to know its own limits; you build a harness that doesn’t need you to. That’s why every agent runs inside a container it can’t escape. You can’t ask an AI agent to self-police or prompt it to be careful. Every decision point is explicit. The agent never merges to main. It opens a branch, opens a PR, and stops there. A human approves and merges. For anything that needs a more formal sign-off, Formicary supports approval workflows with SLAs:

- task_type: security-approval
  method: MANUAL
  approval_policy:
    min_approvals: 1
    sla_deadline: 4h
    timeout_action: ESCALATE
    escalation_recipients: "security-oncall@example.com,vp-eng@example.com"
    escalation_message: "Security approval SLA breached — deployment blocked"
  on_exit_code:
    APPROVED: deploy-prod
    REJECTED: notify-rejected

Secrets live in Kubernetes Secrets, never in ConfigMaps or plain env files. The container runs as non-root (uid 1000). Every artifact is a plain file and every state transition is logged in Formicary.


What Else You Can Build

Everything above is running in production today, but the same architecture supports a much wider range of background agents.

  • Codebase quality agent. A weekly workflow runs ygs-code-review across everything changed in the last week, posts a ranked findings report to Slack, and files tasks in tasks/backlog/ for anything CRITICAL or HIGH.
  • Documentation drift detector. A webhook fires when a PR touching an API handler merges. The workflow checks whether the matching docs were updated.
  • Duplicate abstraction scanner. A periodic workflow compares utility functions across repos owned by different teams and posts “team B has something that looks like what you just built,”.
  • Security posture monitor. Nightly, ygs-security-review runs against everything merged in the last 24 hours that touches auth, authorization, or data access. Findings go to a security channel.
  • Sprint health check, Wednesday afternoons. A mid-sprint cron runs ygs-risk-scan and only posts if it finds something HIGH severity. Most weeks it says nothing.
  • Bug pattern finder. A workflow runs ygs-investigate against recent error logs, proposes the top three hypotheses for each recurring pattern.

Getting Started

Installing the skills locally

git clone https://github.com/bhatti/you-got-skills.git
cd you-got-skills && ./setup

Now any skill runs right in your IDE:

/ygs-standup
/ygs-risk-scan
/ygs-review-pr https://github.com/org/repo/pull/42
/ygs-implement

Loading extra skill repos at runtime

Every job pod can pull in additional skill repos without a rebuild. Set EXTRA_SKILLS_REPOS before running the deploy script.

# 1. Plain URL — sparse-clones only the skills directory (fast, default)
EXTRA_SKILLS_REPOS=https://github.com/myorg/my-skills.git

# 2. Comma-separated — multiple repos in one value, YAML-safe
EXTRA_SKILLS_REPOS="https://github.com/bhatti/you-got-skills.git,skills-cli:nutlope/hallmark"

# 3. JSON array — full control (use for org config; JSON breaks YAML template substitution)
EXTRA_SKILLS_REPOS='[
  {"url": "https://github.com/bhatti/you-got-skills.git", "sparse": false},
  {"url": "nutlope/hallmark", "type": "skills-cli"}
]'

Setup environment variables:

GH_ORG=your-org
GH_REPO=your-repo
GH_TOKEN=ghp_your_token_here
ANTHROPIC_API_KEY=sk-ant-your_key_here
AI_MODEL=claude-sonnet-4-6

# Controls which tracker bare commands like "standup" route to.
# "jira" routes to ai-standup-jira; "github" routes to ai-standup-gh.
DEFAULT_TRACKER=jira

Start formicary server

You can use kubernetes to get Formicary running.

export COMMON_AUTH_JWT_SECRET="<stable-secret-never-rotate>"
export COMMON_AUTH_GOOGLE_CLIENT_ID="<google-client-id>"
export COMMON_AUTH_GOOGLE_CLIENT_SECRET="<google-client-secret>"
export SLACK_BOT_TOKEN="xoxb-..."
export SLACK_APP_TOKEN="xapp-..."

./scripts/deploy-formicary.sh --ec2-ip 10.X.X.X

Connect your ant worker

Jobs run on your own laptop’s local cluster.

./scripts/setup-ant-worker.sh \
  --queen formicary.example.com \
  --token "$FORMICARY_TOKEN"

Deploying the Slack integration

Slack is built into the Formicary queen, so there’s no separate router pod to deploy.

Create a Slack app with Socket Mode

In your Slack app settings:

  1. Enable Socket Mode: generate an xapp- app-level token (scope: connections:write).
  2. Add these bot token scopes under OAuth & Permissions:
ScopePurpose
app_mentions:readReceive @bot mentions
channels:historyRead channel messages
channels:readList channels
chat:writePost messages and Block Kit
groups:historyRead private channel messages
groups:readList private channels
im:historyRead DMs (for the setup registration flow)
im:writeReply in DMs
users:readResolve user display names
  1. Subscribe to bot events under Event Subscriptions:
    • app_mention@bot mentions in channels
    • message.im — DMs, for the setup registration flow
  2. Install to your workspace (needs admin approval if app installs are restricted).

Each developer registers

Anyone who wants Slack commands to work DMs the bot once:

DM to @bot:
setup eyJhbGc...  (Formicary API token from dashboard ? API Tokens)

In any channel the bot’s been invited to:

@bot help          - lists all commands
@bot standup       - runs standup, posts to thread

Summary

The core patterns in this post include declarative pipelines, cron triggers, file-based artifact handoff, exit-code contracts, approval gates. I have used these patterns to automate complex business processing, data pipelines, and CI/CD processes. I am now using it to automate AI backed tasks: a task can read code and form a judgment about it. When the graph, the harness, the sandbox, and the knowledge are four separate layers instead of one tangled system, each one evolves on its own. It allows you to update an environment with configuration, markdown files and configuration. Your job is now to design the graph, define what’s in each node, decide where the sandbox boundary sits, and write down what “good” looks like as a skill. Instead of manually gathering information, you use agents to do the low-level work. You then review the finished product, at the review verdict, at the escalation and decides what matters. That’s conducting, not playing every instrument, and it’s where your judgment actually belongs.


Related Reading

Code

No Comments

No comments yet.

RSS feed for comments on this post. TrackBack URL

Sorry, the comment form is closed at this time.

Powered by WordPress