Task Audit —
running our skills in the cloud
We already have SKILL.md files that check whether a generated task is any good. Today someone runs them by hand, one task at a time. This pipeline runs the same skills automatically, in a throwaway cloud container, for every task in a position.
01What this is
Our tasks are generated by AI, so some of them are broken. The question might be too short, a link might be dead, or the task might simply be impossible to do. We only find out when a candidate complains.
We already know how to check this by hand: open a coding agent on your laptop, point it at a SKILL.md file, and let it go through the task. It works, but it takes about twenty minutes per task and nobody has time to do it for every task.
This pipeline is that same check, done by a machine. When a recruiter creates a position, every task in it is sent off to a small cloud container. Inside, an AI agent reads the task, tries to actually do it, and something independent checks whether the work is right. The team gets one Discord message saying which tasks are fine and which need fixing.
Before — by hand
Someone opens a coding agent on their laptop
Points it at the SKILL.md file
Waits ~20 minutes
Does this for one task, when they remember
Now — automatic
A position is created
The same SKILL.md runs inside a container
All tasks in the position, in one go
Results land in the database and Discord
02The flow
Four steps from "position created" to "results in Discord".
A bit more on each step
- The API call. We send it the list of task IDs. Before doing anything expensive, it checks which tasks have already been checked and haven't changed since — those are skipped. If a task was edited (even just its README on GitHub), it gets checked again.
- Airflow. Think of it as the scheduler. It doesn't do any of the checking itself — its only job is to start the container, watch it, show us the logs, and start it again if the machine crashes. We cap it at two containers at a time so we don't flood anything.
- Fargate. AWS gives us a fresh machine, runs our container on it, and deletes it when the container exits. Nothing is left behind, so every run starts clean.
03Inside the container
The Dockerfile is just a list of what to install so the machine can do the job on its own. Five things matter:
# 1. Python, git and curl — the basics
FROM python:3.12-slim
# 2. Node, then the "pi" coding agent — this is the thing that will do the task
RUN install node ... && npm i -g pi-coding-agent
# 3. Tell pi where to get its AI from: Ollama Cloud, over the internet.
# (On a laptop this points at localhost — there's no such thing in a container,
# so we point it straight at ollama.com. This one line trips people up.)
RUN write /root/.pi/agent/models.json → baseUrl: https://ollama.com/v1
# 4. Our own code and our SKILL.md files
COPY runner.py scripts/ rules/ /app/
# 5. When the container starts, run our script
ENTRYPOINT ["python", "runner.py"]
Notice what is not in there: no passwords, no API keys. Those are handed to the container by AWS when it starts. The image itself is safe to store.
The last line is the important one. Airflow doesn't send code to the container — the code is already inside. Airflow only passes the arguments: which tasks to check, and whether this is dev or prod.
04How it actually solves the task
This is the part people usually want to see. Our script starts the pi agent as a
normal command, the same way you would in a terminal:
pi -p "<the task, written out>" --provider ollama --model kimi-k2.7-code --api-key ...
After that we don't drive it. Pi runs on its own in a loop:
- It sends the task and the files it has read to Ollama Cloud.
- The AI answers with something to do — read this file, change that line, run this command.
- Pi does it, locally, in the copy of the code we downloaded.
- It sends the result back and asks what's next. Repeat until it thinks it's done.
Two details worth mentioning:
- The agent gets the same instructions a candidate gets — the actual task description, not a vague "go fix this". We also tell it to run things and look at the real output before changing anything, so we know its findings are real and not guessed.
- We check the work, not the agent. When it finishes, our script takes over: it puts the original tests back (in case the agent touched them) and checks the result itself. If the task has no tests, a separate AI reads the change against what the task asked for and the answer we have on file. Either way, the agent doing the work never decides whether it passed.
05How a SKILL.md file runs in the cloud
This is the whole point, so it's worth being clear about it.
A SKILL.md file is just instructions written in plain English — "check the title is properly written", "make sure the answer isn't leaked in the starter code", "the question should be between 120 and 1500 characters". When you run a skill on your laptop, your coding agent reads that file and follows it.
In the container it's the same idea, with one difference: instead of a person opening the agent,
our script does it. The SKILL.md files are copied into the image (rules/ in the
Dockerfile above), and at the right moment the script reads the file and hands it to an AI as its
instructions — word for word, nothing rewritten.
On your laptop
You open the coding agent
You point it at SKILL.md
It follows the instructions on one task
You read the result on screen
In the container
The script starts automatically
It reads the same SKILL.md from rules/
It follows the instructions on every task
The result is saved and posted to Discord
Which skills go where
We have three SKILL.md files in the image, and they're used at different moments:
| Skill file | When it runs |
|---|---|
task-audit | While checking the task's details — the field-by-field rules |
task-repo-check | Same moment — the rules for the starter code and the gist |
task-summary | At the end — how to write the report a human will read |
There was also a fourth skill — the one describing how to check whether a task can be solved at
all. That one wasn't about judgement, it was a series of steps: download the code, run the tests,
let an agent try, check the result. Steps like that are better as code, so it became
runner.py rather than a file we hand to an AI.
06What you get back
Every task ends with one of these:
| Result | What it means |
|---|---|
| Pass | Good to give to a candidate. |
| Pass, with notes | It works, but the details need tidying. The notes say exactly what. |
| Fail | The agent genuinely tried and the task couldn't be done — or the guided walkthrough is broken. |
| Couldn't check | We had no way to judge the work. That's our gap, not the task's fault. |
| Nothing to do | The starter code already passes. The task needs to actually be incomplete. |
| Error | Our own machine broke. Not a verdict on the task — it just runs again. |
All of it lands in a task_audits row (kept forever, so you can see a task go from
failing to fixed), the full record goes to S3, and the team gets one Discord message per position
listing what needs attention and how to fix each thing.
07What's left to set up
The code is done and has been tested end to end on dev by running the container locally. What's missing is the AWS side — nothing has been created there yet.
- Create the AWS pieces — somewhere to store the image, the container definition for dev and prod, and permission for our CI to push images.
- Add the keys to that container definition: Ollama, Anthropic (the fallback), GitHub, E2B, AWS, Supabase, and the new Discord webhook.
- Copy the database changes to prod. They're all applied on dev. One of them matters more than the rest: without it the container can't read the template list, and the check that catches broken tasks stays switched off.
- Call the endpoint when a position is created.
- Fix six tasks already live in prod that this audit found — they point at environments that don't exist, so they won't even start for a real candidate.
One thing to expect on the first run: everything gets checked once, because none of it has been checked before. After that, only tasks that actually changed are re-checked.