AI service delivery coordinator
An AI coordinator for service projects. It turns a client request into a plan that a person edits and approves, sets the project up in Asana and HubSpot, then watches delivery and proposes what to do about blockers, missing input and slipping dates.
portfolio projectThis is a portfolio project. I designed and built it myself, to the standard I would hold client work to. No client commissioned it, and the agency and clients in the demo are invented.
This is the kind of work behind my agent workflow build service, from €7,500.
Send a brief- What it is
- An agent system for service businesses and agencies. A request goes in, an approved plan comes out, and delivery is monitored in Asana and HubSpot.
- Type
- Portfolio project, built by Filip. Not a client engagement.
- Stack
- Python 3.12, FastAPI, Reflex, Celery and Redis, PostgreSQL, LangGraph, OpenAI models, Docker Compose.
- Status
- v1.0 released. The repository is private. There is a walkthrough on YouTube, and a live demo on request.
- What to look at
- The demo loop, walkthrough and screenshots, the six decisions with their evidence and the test table.
What the film shows
Silent, about 43 seconds. Demo build screens: made-up clients, stand-in Asana and HubSpot, a different seed project in each step. The sign-off sheet is an illustration of the approval record, not a product screen.
A client request arrives.
It comes in through the chat.
The coordinator’s chat page. The first suggested request, “Launch campaign for a new coffee line” for Northwind Coffee Roasters, is marked.
The Planning agent drafts a plan.
Dates come from plain code.
The plan’s task list appears row by row, then three dates are underlined: 12 Oct 2026, 13 Oct 2026 – 14 Oct 2026 and 15 Oct 2026 – 16 Oct 2026.
Then it waits for a person.
Nothing reaches Asana yet.
The approvals page, with the one plan waiting for approval marked.
A person edits it, then signs it off.
Approval names the exact version.
The plan opens and “Version 2 · edited by a person” is underlined. Approve plan is pressed, and a sign-off sheet is drawn: drafted by Planning agent, Version 1; edited by a person, Version 2; approved by alex.morgan, Version 2, signed and ticked.
Only then is the project created.
Each object is created exactly once.
The project page, with “Synced to Asana” and “Synced to HubSpot” underlined.
Later, a blocker comes back from Asana.
The system shows what slips.
Further down the same page, “A blocker was reported in a comment.” is boxed, and the impact under “If it stays blocked 5 more working days:” is marked.
It proposes options. A person approves one.
Nothing changes without approval.
Two suggested actions. The person picks “Remove a dependency”, and a check is drawn beside it.
Keel Agents
AI agents that do the work and wait for your sign-off.
The end card: the Keel mark, the name and the line above.
~6,800tests
Unit and integration tests, from the project’s README.
source: project README
85browser tests
Playwright in Chromium. CI also runs both Docker Compose stacks.
source: project README
The problem
When a client sends a request, someone on your team has to turn it into a plan: what to do, in what order, by whom, by when. They set the project up in the project tool and the client up in the CRM. Then, for weeks, they watch for:
- a comment saying work is blocked
- a task that is overdue
- a date that has quietly moved
For each one, they decide what to do.
That is coordination work:
- Slow. It is made of reading, planning and re-planning.
- Risky. The mistakes show: a date promised without checking the dependencies, a project created twice, a message sent that nobody reviewed.
A language model can read a request and draft a plan. It should not choose dates, write to your client’s CRM or send anything on your behalf without a person agreeing first. So I set myself a narrower problem than “automate project management”: let the model do the reading and drafting, and build the system so that nothing reaches Asana or HubSpot until a person approves.
What I built
The system follows one loop, and a person sits at each point where something would leave it.
- Request. Someone on the team, the operator, enters a client request in the chat.
- Plan. The Planning agent drafts milestones, tasks, dependencies, roles, assumptions and missing information. Plain code computes every date.
- Edit and approve. A person edits the plan freely. The system warns about problems, such as date order, cycles and tasks past their milestone, but never overrides the edit. Approval records who approved which version.
- Set up. Only then is the project created in Asana, with sections per milestone, tasks with dates and dependencies, and the client’s company, contact and deal in HubSpot. Each object is created exactly once.
- Watch and propose. Changes in Asana come back as events. Plain code detects overdue tasks, moved dates and deleted tasks, and forecasts which milestones and which target date are at risk. The agents read new comments for blockers and requests for input, then propose corrective or recovery options and follow-up drafts. A person approves each one before anything is changed or sent.
It handles four kinds of issue: a blocker, missing input, a schedule change and a date at risk. Four agents share the work, and one of them makes no model call at all.
| Agent | What it does | Model call |
|---|---|---|
| Orchestrator | Decides which step follows which event, and whether the project may take it | None |
| Planning | Reads the request, drafts the plan and lists assumptions and missing information | A draft, plus one repair if needed |
| Monitoring | Classifies comments; suggests corrective options for blockers and recovery options for dates at risk | Classify and suggest |
| Communication | Drafts follow-ups for the team or the client. Always drafts | A draft |
How it works
The diagram shows the route one project takes. Grey is the system working, orange is a person deciding and green is a state reached. The approval gate sits in the middle of it, and nothing on the lower half of the drawing happens until it has been passed.
Behind it, the API never calls a vendor or a model inside a request. Anything slow or external is a job, run by workers from a queue stored in the database. The interface talks to the API only, server to server. Each step is stored as four separate records: what the AI reasoned, what it proposed, who approved it and what the side effect did.
The system worksA person decidesState reachedDashed: it repeats for as long as the project runs
See it run
Everything below comes from the demo. It runs the whole system with stand-in versions of Asana and HubSpot and recorded agent answers, so it needs no accounts, keys or spend, and nothing a visitor does can reach a real external system. It is seeded with an invented small agency and five clients at different stages.

- Approve plan, still available (a person acts)
- Warnings shown, not blocking (system fact)
A plan a person has edited. Look at the banner: nine warnings, and the Approve button is still there. The system points out problems in the edit and leaves the decision to the person. Approval will name this exact version.

- Waiting for a person to decide (system fact)
The approval inbox. A plan and a drafted follow-up comment are waiting. Neither has been written to Asana or HubSpot, and neither will be until a person decides.

- Impact worked out by plain code (system fact)
A blocker on a delivered project. The impact line, “if it stays blocked 5 more working days”, is worked out by plain code from the task dependencies. The agent only suggests what to do about it, and each suggestion starts as a draft.
Six decisions and their evidence
Each decision answers a way these systems usually go wrong. The note beside it says what the project does about it. The repository is private, so I describe the evidence rather than link to it.
Nothing reaches Asana or HubSpot before a person approves
This is the rule the whole system is built around. An approval names the operator and the exact plan version, and if the plan is edited afterwards it goes back to planning. Because reasoning, proposal, approval and side effect are stored separately, you can answer “who agreed to this?” afterwards.
evidence
No write to either system is planned before an approval exists. An approval names one plan version, which must be the latest. The four stored records are an agent run, a proposal, an approval and an external action with its attempts. Client messages are always drafts.
The model drafts; plain code does the dates
A language model is good at reading a request and drafting a plan, and unreliable at arithmetic and constraints. So the model only interprets and drafts. Dates, dependencies, scheduling back from a target date, the impact of a slip and the forecast are ordinary Python, and whatever the model returns must be validated before anything uses it.
evidence
Model output is a typed Pydantic model in OpenAI’s strict structured-output mode, validated before use. The Planning agent makes one draft call and at most one repair. The Orchestrator, which decides what runs next, makes no model call.
Every write to a client system happens exactly once
An automation that retries a failed call can create the same project twice. Here each external write has a deterministic idempotency key, and its attempt is saved before the call is made. If the outcome is unknown, because of a timeout or a worker killed mid-call, the system looks at the vendor before trying again. A failure is shown as a failure, with what to check. Nothing is reported as synced unless every action succeeded.
evidence
An unknown outcome is settled against the vendor: Asana by a marker in the object, HubSpot by the idempotency marker or the email. One match is the object, none means try again, and several stop for a person. Keys take the form owner:operation:subject.
Whatever a client writes is treated as untrusted
Client requests, task comments and CRM records can contain instructions aimed at the model. They reach it only as quoted data in the user message, never in the system prompt, and the agents have no tools to call. Jobs between services carry identifiers only, so no untrusted text crosses the queue.
evidence
The agents have no tools, their output is validated, and anything that would be written to Asana or HubSpot waits for approval. This is ordinary prompt-injection defence, and it is why the audit service lists it first.
Duplicate, late and out-of-order webhooks change nothing they should not
Asana reports changes through webhooks, which can arrive twice, late or in the wrong order. Events are verified, stored once and processed as jobs, each compared with the stored task state. A periodic check against Asana’s own listing is the source of truth, and a dead webhook is replaced.
evidence
The signature is verified over the raw bytes. A malformed event is stored as ignored, with a reason, and its neighbours carry on. Webhooks are the fast path; the listing is the truth.
Spend is capped in code
Every model call reserves tokens against a per-run cap and a daily cap before the call is made. The limit is enforced where the call happens, not in a prompt asking the model to be economical.
evidence
All model calls go through one function that makes the reservation first. The evals that run against the live model carry their own hard token cap.
What I tested, and how
The tests are layered the way the system is. The vendors are faked at the HTTP transport layer, so the real Asana and HubSpot adapters run in the tests. Agent behaviour is covered by scenario evals against the live model, graded, under a hard token cap.
| Layer | What it covers | Size |
|---|---|---|
| Unit and integration | Domain rules, use cases against PostgreSQL, adapters against fake HTTP transports, the API, UI state, migrations, workers against Redis | About 6,800 tests |
| End to end | Playwright in Chromium, against the running API and UI | 85 browser tests |
| Compose | Smoke tests against the built Docker images, for the live-configured stack and the demo stack | Both stacks |
| Agent evals | Agent scenarios against the live model, graded, under a hard token cap | Run separately |
CI runs lint, format, strict mypy, the tests (a second time at a far-future date, so seeded dates never expire), the browser tests and both Compose stacks.
The layering is enforced by a test, not by convention: the domain and application code import none of SQLAlchemy, FastAPI, Celery, LangGraph, OpenAI or the settings.
The repository is private, so these figures come from the project’s README and you cannot count them on this page.
What it does not do
- There is no hosted demo. It runs locally with Docker, and a live demo is available on request.
- The demo is staged. It uses stand-in vendors and recorded agent answers, and its chat accepts the three suggested requests only. The real Asana and HubSpot adapters are tested against faked HTTP transports.
- Some revisions are not fully synced. Project-level changes, a revised title or target date, stay in the plan and are not yet written to Asana or HubSpot. Tasks and sections are.
- Recovery has three kinds: remove a dependency, add a task, or wait. Shortening a task or moving a milestone on purpose is not offered yet.
- A vendor object deleted by hand is not recreated. The sync stops and says what to check.
- Agent edge cases remain. On a strict chain of tasks, corrective options occasionally offer a task that cannot help.
- It has not been deployed publicly.
- Not done: a public host, TLS, a reverse proxy that passes websockets, backups and a secret store.
- Not reached by a local demo: retention of stored webhook events, reconcile paging for very large projects, and a few job-queue inefficiencies while workers are down.
- On the roadmap and not built:
- notifications
- client email that is drafted, approved and then sent
- scope-change detection
- conversational commands
- a per-project audit history
What this means for your business
If your team spends its week turning requests into plans and chasing status, this is the shape of what I would build for you: an agent that reads and drafts, plain code that does the arithmetic, and a person who signs off before anything leaves the building. I built it against Asana and HubSpot. If you use other tools, the integration layer is where the work goes, and I would tell you so in my first reply.
- Agent workflow build
- Agent workflow builds start from €7,500. I would scope the slice of this that matches your workflow, with the approval points you choose.
- LLM hardening audit
- The spend caps, untrusted-input handling and evals above are what I look for in an existing LLM feature. Audits start from €1,500.
- RAG assistant
- For answers drawn from your own documents, see the EU AI Act assistant, my other portfolio project.
