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 code | Meaning | What Formicary does |
|---|---|---|
0 | Success | Move on to the next task |
1 | Error (worth retrying) | Retry with backoff |
2 | Blocked for a human input | Pause the job indefinitely |
3 | Not finished yet / waiting on something | Pause, 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:
| Phase | Skills |
|---|---|
| Requirements | ygs-refine-prd, ygs-review-prd, ygs-refine-trd, ygs-review-trd |
| Architecture | ygs-refine-architecture, ygs-review-architecture, ygs-spike |
| Planning | ygs-sprint-plan, ygs-wbs, ygs-estimate, ygs-triage |
| Execution | ygs-implement, ygs-qa, ygs-ship, ygs-uat |
| Review | ygs-review-pr, ygs-code-review, ygs-security-review, ygs-sre-review, ygs-api-review, ygs-ui-review |
| Team intelligence | ygs-standup, ygs-risk-scan, ygs-pr-queue, ygs-sync |
| Learning | ygs-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:
| Signal | Default severity | Escalates to HIGH if… |
|---|---|---|
| Issue stale > 3 days | MEDIUM | it blocks another issue |
| Issue stale > 5 days | HIGH | — |
| PR open > 2 days, no review | MEDIUM | only one reviewer assigned |
| PR open > 4 days | HIGH | — |
| Person silent > 2 days | MEDIUM | also no tracker activity |
| Blocked label | HIGH | — |
| Dependency chain: upstream is stale | HIGH | — |
| Sprint ends in < 2 days, not started | HIGH | — |
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:
- Correctness: logic errors, null handling, incomplete enum handling, partial failure, race conditions, off-by-one errors
- Security: injection vectors (SQL, command, XSS, SSRF, path traversal), auth/authorization, data exposure across tenant boundaries, etc.
- API surface: breaking changes, contract violations, backwards compatibility, versioning
- 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_status | Exit code | What the pipeline does |
|---|---|---|
APPROVED | 0 | Proceed to create-pr |
NEEDS_FIX | 0 | Claude fixes it inline, then create-pr |
BLOCKED | 2 | PAUSE_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:
- Reads your Slack user ID (
U0A1HQL0C9J) off the Socket Mode event. - Looks up
slack_user_id = U0A1HQL0C9Jinuser_configsand finds your Formicary user record, including yourUserIDandOrganizationID. - Calls
SaveJobRequest(qc, req)and the server overwritesrequest.UserIDandrequest.OrganizationIDfrom that context. - 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 type | What runs |
|---|---|
@bot standup | Daily brief: per-person status, risks, discussion questions (routes to Jira or GitHub via DEFAULT_TRACKER) |
@bot risk / @bot risks | Ranked sprint risks with a capacity check |
@bot prs / @bot open prs / @bot review queue | Open 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-123 | Full pipeline: plan ? implement ? self-review ? PR, for a Jira issue |
@bot implement 42 | Same, 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-2 | Claude analyzes root cause + possible fixes for Jira issues |
@bot gh-query <term> | Search open GitHub issues by keyword |
@bot gh-analyze #123, #456 | Claude analyzes root cause + possible fixes for GitHub issues |
@bot adhoc <free text> | Run any Claude skill with a freeform prompt |
@bot help | List 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:
- Looks for the skill in a few candidate locations (
/workspace/skills,~/.claude/skills/you-got-skills/skills,~/workplace/you-got-skills/skills). - Writes
.ygs/tracker.ymldynamically from environment variables (Jira or GitHub config, team members, sprint info). - Invokes Claude with the skill content and your prompt.
- Strips Markdown formatting from the output so it renders cleanly in Slack.
- 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(orBITBUCKET_WORKSPACE): the team/area filter value.JIRA_TEAM_FIELD: the Jira custom field name (defaultEngScrumTeam, 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-reviewacross everything changed in the last week, posts a ranked findings report to Slack, and files tasks intasks/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-reviewruns 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-scanand only posts if it finds something HIGH severity. Most weeks it says nothing. - Bug pattern finder. A workflow runs
ygs-investigateagainst 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:
- Enable Socket Mode: generate an
xapp-app-level token (scope:connections:write). - Add these bot token scopes under OAuth & Permissions:
| Scope | Purpose |
|---|---|
app_mentions:read | Receive @bot mentions |
channels:history | Read channel messages |
channels:read | List channels |
chat:write | Post messages and Block Kit |
groups:history | Read private channel messages |
groups:read | List private channels |
im:history | Read DMs (for the setup registration flow) |
im:write | Reply in DMs |
users:read | Resolve user display names |
- Subscribe to bot events under Event Subscriptions:
app_mention—@botmentions in channelsmessage.im— DMs, for thesetupregistration flow
- 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
- Killing the State Machine: Declarative AI Coding Agents with an Orchestration System
- Building Production-Grade AI Agents with MCP and A2A
- Building a Production-Grade Enterprise AI Platform with vLLM
- Agentic AI for Personal Productivity: Building a Daily Minutes Assistant with RAG, MCP, and ReAct
- Agentic AI for Automated PII Detection with LangChain and Vertex AI
- Agentic AI for API Compatibility with LangChain and LangGraph
- AI Writes Code, You Own the Design
- Building a Distributed Orchestration and Graph Processing System (Formicary)