The spark
Every AI tool we used had the same tell. You ask a real question, it thinks behind a spinner for a second, and hands you one confident answer. No sources you can trust, no way to see how it got there, no way to steer it, and no idea what it cost until the bill lands.
That is fine for "write me a tweet." It is useless for the questions that actually matter. Should we partner with this company? What does this market really look like? Is this claim in the report even true? For those, a black box answer is not an answer. It is a guess with good grammar.
So we built A Pack. Not another chatbot. A research team you can watch work.
You give it a question, or your own documents. A pack of specialized agents goes to work live on a canvas in front of you: Alpha plans it, Scouts range out for ground truth, a Tracker shapes what they bring back, a Howler writes it up, and a Sentinel attacks it. You approve the plan, edit the team, and answer questions as they come up. Every claim links back to a source you can click. The Sentinel challenges the weakest claim before anything ships, and it is allowed to say "I could not verify this" instead of padding. And you set a dollar Boundary the pack can never cross.
How we built it
A Pack is three pieces talking through an event spine.
- A Python / FastAPI engine runs the agents on Qwen models through Qwen Cloud. Every meaningful thing an agent does becomes an event.
- Those events are the single source of truth. We used an event-sourced,
transactional-outbox design: the engine writes an event to Postgres and
signals it in one transaction, a relay claims it with
FOR UPDATE SKIP LOCKED, pushes it to Redis Streams, and a small Rust / Axum gateway fans it out over WebSockets. - The React canvas replays those events through one pure reducer, so what you watch is exactly what happened, in order. Never a fake progress bar.
Cost control sits in front of every single model call. Before each call the engine estimates the spend and refuses to cross your Boundary:
The rule is simple: halt when what you have already spent, plus the estimated cost of the next call, would reach your Boundary. It warns at 70%, eases down to a cheaper model tier at 85%, and hard stops at 100%.
It also runs fully offline against a fake model, then swaps to real Qwen with zero code change, so we could build and test the entire flow before spending a cent.
What we learned
- Transparency is an architecture decision, not a UI trick. You cannot bolt "watch it live" onto a black box. The event spine is what makes the whole thing honest.
- Making an LLM trustworthy is grounding plus a critic that is allowed to fail. Tie every claim to a source ID, then let one agent try to tear it down. "I could not verify this" is a feature, not a bug.
- Cost has to be a first-class constraint, checked before the call, not counted after it.
- We also wrote the faceted wolf mascot as a hand-built WebGL mesh from our logo geometry, which taught us more about shaders than we planned to learn.
The hard parts
- Real-time with no lies. Getting events delivered at-least-once and gap free, so the canvas never drops or reorders a step, took the transactional outbox and a single sequence authority to get right.
- Halting a moving pack. When several agents run in parallel and all cross the Boundary at once, naive halting deadlocks. We had to rebuild it around one hunt-level latch that stops everyone and resumes them together.
- Keeping it honest under pressure, guarding against prompt injection from the very web pages the Scouts read.
- The biggest one was not technical. It was scope. A team this small building an engine, a gateway, a live canvas, and an accountability layer in nine days meant cutting things we loved and shipping the spine properly instead. We chose depth over surface, and we shipped.
Built With
- alibaba-cloud
- alibaba-cloud-ecs
- alibaba-cloud-oss
- axum
- dashscope
- docker
- fastapi
- model-studio
- nginx
- postgresql
- pydantic
- python
- qwen
- qwen-cloud
- react
- react-flow
- redis
- rust
- tailwindcss
- tanstack-query
- tokio
- typescript
- vite
- websocket
- zustand


Log in or sign up for Devpost to join the conversation.