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.
- 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.
~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 the comment that says work is blocked, the task that is overdue and the date that has quietly moved, and decide what to do about each one.
That is coordination work. It is slow because it is made of reading, planning and re-planning. It is risky because 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 written.
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.

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.

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.

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 the evidence for each
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.
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.
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.
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.
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.
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.
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. Also 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 and 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.
- AI agents and workflow automation
- 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 development
- For answers drawn from your own documents, see the EU AI Act assistant, my other portfolio project.
