Both chatbots and agents can talk. The difference is whether the system’s job ends at a reply or continues until a task in the world is done. Confusing the two leads to either under-powered “agents” that only chat, or over-armed chatbots that should never have had write access.
Chatbot: “How do I reset my password?” → explains steps. Agent: verifies identity → triggers reset link → updates the ticket → confirms completion.
Same surface (a chat window), different contract with the user. Chatbots optimize for helpful messages. Agents optimize for completed outcomes — and therefore need tools, loops, and stop rules.
| Aspect | Chatbot | Agent |
|---|---|---|
| Main function | Answer questions | Complete tasks (and answer) |
| Tool integration | Optional / limited | Core capability |
| Execution style | Mostly single turn | Multi-step loop |
| Autonomy | Low | Higher (scoped) |
| Success metric | CSAT, deflection | Task success rate |
| Failure mode | Wrong advice | Wrong side effect |
State is conversational. Side effects, if any, are rare and manually triggered by the user following instructions.
State includes plan progress and external system status (“ticket #442 closed”).
Many apps are chatbots with a few agentic buttons: “Create the ticket for me.” That is fine. Label autonomy honestly. When the UI promises “I’ll handle it,” you owe the user agent-grade controls: confirmations, receipts, and undo paths.
Choose a chatbot when:
Choose an agent when:
Password reset again:
start_reset(user_id) after auth checks; writes audit log; replies with “link sent to the email on file” without exposing the address fully.If your “agent” cannot call start_reset, it is a chatbot wearing a cape.
Two handlers, same user utterance — different contracts.
def chatbot_password_reset(user_msg: str) -> str:
return (
"To reset your password: open Settings → Security → Reset. "
"We will email a link. I cannot trigger it from chat."
)
def agent_password_reset(user_id: str, verified: bool) -> dict:
if not verified:
return {"status": "need_verification", "next": "otp_challenge"}
# host-side tool — not free-form model text
receipt = {"action": "start_reset", "user_id": user_id, "ok": True}
return {
"status": "done",
"user_visible": "A reset link is on its way to your email on file.",
"audit": receipt,
}
print(chatbot_password_reset("reset my password"))
print(agent_password_reset("u_123", verified=True))
Product question: which return type did you promise in the UI copy?
Audit your UI strings. Phrases like “I’ll take care of that” imply agent semantics; “Here’s how you can…” implies chatbot semantics. Mismatch is a trust bug. For each flow, pick a primary metric: deflection/CSAT for chatbots, task success and undo rate for agents. Review weekly which metric moved after prompt or model changes.
A useful migration path is progressive disclosure of agency: answer first, then offer a button “Do this for me” that enters the tool loop with an explicit receipt. Users who only wanted information never pay the latency or risk tax; users who wanted completion get a clear contract. That hybrid covers most product surface area without pretending everything is fully autonomous.
Some products sit on the line: a FAQ bot that can create a ticket is mostly a chatbot with one agentic escape hatch. Treat each side effect as its own mini-agent contract — schema, auth, receipt — even if the rest of the UI stays conversational. Conversely, an “agent” that only drafts pull-request text without opening the PR is still a chatbot for shipping purposes. Name capabilities after what changes in external systems, not after the model vendor’s marketing tier.
Use chatbots to inform and guide; use agents when the product must execute multi-step, tool-backed work to a checkable outcome under policy and budgets.