The first version of Klickbee's AI came down to one simple idea: a prompt, a response. The user writes a brief, we drop it into a big prompt, and the model returns a structured object — a roadmap with its milestones and tasks. Technically: a single generateObject call, a Zod schema to guarantee the shape.
And honestly, it works. To turn “redesign the marketing site” into a plausible roadmap, a good prompt is enough. Fast to write, easy to understand, cheap.
The problem: this model hits a ceiling the moment the task gets real.
Actually using the product, four flaws keep coming back. None of them is fixed with “a better prompt” — they are architectural.
Rebuilding a GitLab repo's roadmap from a plain prompt yields a believable fiction, not what actually happened. It has no access to commits, merge requests, issues.
Every exchange starts from scratch. The user explains a constraint, and two messages later the AI asks for it again. It turns into an interrogation.
A one-shot returns its first answer, good or bad. No stepping back, no self-critique.
One mistyped field from the model and the whole turn dies with a “Failed to parse” that means nothing to the user.
“Agent” is not a buzzword for “a slightly smarter prompt.” The real shift is going from generating an answer to carrying out a task. In Klickbee, every important AI became a graph (LangGraph) with explicit steps.
The whole thing is durable — state persists in Postgres, a run survives a crash or a later resume — and traced: every step, every LLM call is visible in Langfuse to debug and score.
answers.
gathers, decides, verifies, collaborates, and resumes.
The brief now runs through gather → clarify → draft → review → validate → apply. The draft node is itself agentic: a loop where the model calls its tools to ground itself before writing. If a key detail is missing — deadline, scope — it asks a question instead of guessing. Then it reviews and corrects itself before showing anything.
Here grounding changes everything. The agent reads the real commits, merge requests and issues, autonomously, and reconstructs a roadmap faithful to what was actually shipped — instead of an invented story.
Telling detail: the GitLab API returns no commit total on some endpoints; we had to paginate to stop showing “100 commits” forever. Agentic work forces you to face the real thing.
This is where the lesson was sharpest. The first version interrogated the user, one question after another, forgetting previous answers. We flipped it to act-first: act by default with explicit assumptions, ask only on a real blocker, and remember the whole conversation. Like a good colleague, not a form.
The newest one: you describe “who works on what,” the agent labels the roles, composes the teams, and — because it reads the existing roster — reuses the people and teams already there instead of creating duplicates. It asks a question when two people step on each other.
What agentic buys you, concretely:
Let's be honest about the bill: more complexity (a graph, nodes, tools), more tokens (several calls instead of one), more infra (a database for state, tracing). A one-line generateObject becomes a subsystem.
The real click isn't technical, it's a change of posture. You stop asking “what prompt will give me the right answer?” and start asking “what steps would a competent person follow to do this work?” — then you give the AI the tools, the memory and the checkpoints to follow those steps.
In Klickbee, that's what took the AI from a demo that impresses to a tool you can actually work with.
There's an ambiguity I took a while to clear up myself: a LangGraph graph is not automatically an agent. Anthropic draws the sharpest line I know — a workflow orchestrates LLMs and tools along predefined code paths, whereas an agent lets the model direct its own process and decide which tools to call. In Klickbee, the overall graph stays largely a workflow (gather → clarify → draft → review → apply, fixed edges), but the draft node is genuinely agentic: a loop where the model chooses to call this tool, loop again, or stop.
The rule of thumb: stay in workflow mode as long as the path is known in advance, and only open an agentic zone where the “what next” decision genuinely depends on what the model just discovered. Every autonomous turn costs latency, tokens, and lets an early mistake propagate.
The durability I mentioned isn't a vague “we save to a database.” LangGraph models it with two precise notions. A checkpointer persists graph state at every superstep, tied to a thread_id: that's what lets a run survive a crash and resume exactly where it stood. And a store serves the durable memory that spans threads — the preferences and decisions that must survive from one conversation to the next. The adjustment chat's memory is the store; resuming after an interrupt is the checkpointer.
Human-in-the-loop had the subtlest trap in store for me. When a node calls interrupt() with a JSON-serializable value, the graph freezes its state and waits indefinitely; you resume later, possibly in a different HTTP request, with Command(resume=…) and the same thread_id — the human answer becomes the return value of interrupt. The trap: on resume, the node restarts from the beginning, not from the interrupt line. Any code that ran before is replayed. The lesson: put side effects (writes, non-idempotent API calls) after the interrupt point, not before.
“It reviews itself” deserves to be taken apart, because it's a named, studied pattern, not a homemade trick. Anthropic calls it evaluator-optimizer: one LLM produces, a second evaluates and returns feedback, you loop. It only shines under two conditions — clear evaluation criteria, and measurable gain per iteration. Roadmap generation meets both: “does every task have a parent milestone? are there duplicates? is the brief's scope covered?” are checkable questions.
The most ambitious version of this pattern is called Reflexion. LangChain describes it well: instead of purely internal self-critique, the actor grounds its criticism in external data — it fetches evidence before judging itself. The Reflexion guide breaks it into three roles: an actor that produces, an evaluator that scores the trajectory, and a self-reflection step that turns that score into a verbal instruction for the next round. That's exactly what my draft node does when it re-reads the real GitLab commits before validating a plan.
the model judges itself on what it “thinks.”
the model fetches evidence before judging itself.
A loop with no guardrail either spins forever or blows up the bill. Three protections: an iteration cap (MAX_REVIEWS), a clear stop condition (“review.ok === true”), and a validated structured output every round. On my side the Zod schema pulls double duty: generation fails rather than returning an invalid object, turning a silent “Failed to parse” into an error you can catch and retry.