08-31-2026

Thinking About Intent Recognition: Plan + Tool Design

I did many rounds of intent recognition, and finally found: the problem may not be intent recognition at all. Lately I’ve been iterating on intent recognition in a “schedule calendar” product.

At first I thought this was just an ordinary classification problem:

The user says a sentence, the model judges what they want to do.

For example:

  • “明天下午三点帮我开个会” → create schedule
  • “我明天有什么安排?” → query schedule
  • “把下午三点的会议取消掉” → delete schedule
  • “改到四点吧” → update schedule

Seems simple enough.

But once it went into a real product, I found a very annoying problem:

Bad cases never run out.

Fix “明天下午开会”, and “明天下午帮我留两个小时” breaks.

Fix single-turn, and multi-turn clarification breaks.

Fix intent classification — the model knows the user wants to create a schedule, but doesn’t necessarily call the create-schedule tool.

So development gradually became:

find a case → change the prompt → test → fix → a new case appears → change the prompt again……

After many rounds, I started to realize:

This may no longer be a prompt-engineering problem, but an agent-architecture problem.


1. The real intent often isn’t in the user’s last sentence

A calendar is a very typical multi-turn interaction.

For example, the user says:

Help me schedule a product discussion.

The system can’t create directly, because time is missing.

So the agent asks:

When would you like it?

The user replies:

Tomorrow afternoon.

The agent continues:

What time in the afternoon?

The user replies:

Three.

If you only look at the last sentence “三点”, you can hardly tell what the user wants.

The real intent is distributed across the whole conversation history:

User: 帮我安排一个产品讨论
Assistant: 你希望安排在什么时候?
User: 明天下午吧
Assistant: 下午几点?
User: 三点

What we ultimately need to recover is:

Action: CreateEvent

Title: 产品讨论
Date: 明天
Time: 15:00

So I now lean more and more toward:

Don’t classify every single user message independently too early.

Give the model a recent stretch of conversation history together with the current input, and let it judge:

What is the user actually trying to accomplish right now?

In other words, the input to intent recognition isn’t:

User Message

but:

Conversation Context
+
Current User Message
+
System Context

That’s the first change.


2. Don’t make “intent recognition” the center of the system

My initial approach was fairly traditional.

The model outputs a JSON:

{
  "intent": "create_event",
  "title": "产品讨论",
  "date": "tomorrow",
  "time": "15:00"
}

Then the program, based on the JSON:

LLM

Intent JSON

Business Code

Calendar API

This architecture is very easy to understand.

But as capabilities grew more complex, it started to feel heavy.

Because real-world user requests can’t be fully described by a few enums.

For example:

明天下午找个我和 Alex 都有空的时间聊一下项目,大概一个小时。

This involves at least:

understand intent

parse date

query my calendar

query Alex's availability

find common free time

create event

At this point “intent” is no longer just:

CREATE_EVENT

but a series of actions to complete.

So I gradually adjusted the architecture to:

Conversation

     LLM

   Agent Loop
   ↙   ↓   ↘
Query  Create  Update ...
Calendar Event Event

Intent no longer necessarily needs to be an explicit intermediate artifact.

After the model understands what the user wants, it can directly decide what tool to call next.

This is a shift from:

Intent Classification

toward:

Agent Decision Making.


3. Turn calendar capabilities into tools, not infinitely expanding intents

So I started registering the calendar’s basic capabilities as tools.

The most basic is CRUD:

query_calendar
create_event
update_event
delete_event

Each tool has clear:

name
description
parameters
return value
error

For example:

create_event(
  title,
  start_time,
  end_time,
  participants,
  ...
)

Once the agent has these capabilities, it decides itself when to call them.

For example:

帮我看看明天下午有没有空。

Agent:

→ query_calendar()
→ return result
→ answer the user

While:

明天下午三点帮我安排一个产品讨论。

is:

→ create_event(...)
→ return success
→ tell the user it's created

If information is incomplete:

帮我安排一个产品讨论。

The agent calls no tool, and directly answers:

你希望安排在什么时候?

User:

明天下午三点。

The second round’s model gets the conversation context again:

User: 帮我安排一个产品讨论
Assistant: 你希望安排在什么时候?
User: 明天下午三点

Now the information is complete:

→ create_event(...)

The biggest change of this mechanism:

Clarification itself is part of agent behavior, not something requiring a separate huge state machine.


4. Tool-calling failure doesn’t mean agent failure

Next came another real problem.

Tools fail.

And for many reasons.

For example, the model calls:

create_event(...)

The result may be:

INVALID_ARGUMENT

or:

TIME_CONFLICT

or even:

DATABASE_TIMEOUT

These three errors are all “tool call failed”, but the handling is completely different.

So I now lean toward structurally classifying tool errors.

For example:

Tool Error

    ├── Parameter Error
    │       ↓
    │    Agent Retry

    ├── Business Error
    │       ↓
    │    Agent Reasoning

    └── System Error

       Fail + Observability

Parameter error

For example:

start_time format invalid

This error can be returned to the model.

The model may realize:

So the tool wants ISO 8601.

Then regenerate the parameter and retry.

The user doesn’t need to know the first call failed.


Business error

For example:

TIME_CONFLICT

This isn’t a system fault.

The agent can continue:

create schedule

find conflict

query nearby free time

recommend alternative times to the user

Even further in the future:

This time conflicts with your weekly meeting. 16:00 and 16:30 are free — want to move it to one of them?

This is real agent reasoning.


System error

For example:

Database Timeout
Internal Server Error
Network Error

The model can’t solve this.

At this point you should:

stop retrying
+
return user-understandable info
+
write to observability
+
trigger monitoring

Then the developer locates it from the trace.

So:

Agent retry isn’t a universal retry — it should be built on an error taxonomy.


5. After this, I found another problem: ReAct drifts

If the whole system is just:

Think

Act

Observe

Think

Act

Observe

the classic ReAct loop, then simple tasks work great.

For example:

创建明天下午三点的会议。

probably needs only one tool call.

But once tasks get complex, problems appear.

For example:

把我明天下午所有会议往后推一个小时,如果有冲突就找附近的空闲时间,重要会议不要动。

The agent may need:

query calendar
→ decide which meetings can move
→ modify A
→ query conflict
→ modify B
→ re-query
→ modify C
→ ...

Once the execution chain gets long, the model easily drifts from the user’s original goal.

This is a very important problem we discussed today:

The ReAct loop easily causes goal drift.

The model makes each decision based on the latest observation.

But five or six steps in, it may have forgotten part of the original constraints.

For example:

“重要会议不要动。”

may get weakened later in execution.

So we talked about the Planner.


6. Three Planner implementations

Currently I think there are three typical ones.

Option 1: Planner built into the agent loop

The first round forces plan generation:

User

Plan

ReAct

Tool

Observation

ReAct

For example:

Goal:
调整明天下午的会议

Plan:
1. 查询明天下午所有会议
2. 判断哪些会议允许移动
3. 每个会议顺延 1 小时
4. 检查冲突
5. 冲突时寻找最近空闲时间
6. 不修改重要会议

After that the whole ReAct loop carries this plan.

Its biggest value:

Continuously pulling the agent’s attention back to the original goal.


Option 2: independent Planner agent

Split Planner and Executor completely:

             ┌─────────────┐
User ───────→│ Planner     │
             └──────┬──────┘

                  Plan

             ┌─────────────┐
             │ Executor    │
             └──────┬──────┘

                  Tools

Planner is responsible for:

What should be done?

Executor is responsible for:

How to execute it?

This has very clear responsibilities and suits complex agents.

But the cost is obvious:

One or more extra model calls.

Latency, token cost, and system complexity all increase.

For “明天下午三点提醒我开会”, it’s clearly unnecessary.


7. The third option is my current favorite: Planner as a Tool

There’s an interesting design:

Register the Planner itself as a tool.

For example:

Tools:
- query_calendar
- create_event
- update_event
- delete_event
- create_plan

The agent receives:

明天下午三点提醒我开会。

It finds this very simple:

create_event()

execute directly.

No planner needed.

But if the user says:

把我下周所有和项目 A 有关的会议重新整理一下,尽量集中到周二和周三,但不要影响已经确认的重要会议。

The model judges:

This task is complex.

So:

create_plan()

and gets:

1. 查询下周会议
2. 筛选项目 A
3. 标记不可移动会议
4. 查询周二、周三空闲时间
5. 生成迁移方案
6. 检查冲突
7. 请求用户确认
8. 执行修改

Then enters the ReAct loop.

So the whole architecture becomes:

                 User

                 Agent
              ↙     ↓     ↘
         Simple   Complex   Clarify
           ↓        ↓         ↓
         Tool    Planner    User

                  Plan

                  ReAct

                  Tools

Why I like this option now:

Simple tasks stay simple; complex tasks pay the planning cost.

The Planner went from a fixed step in the agent flow, to an on-demand cognitive capability.


8. Future evolution: hierarchical planning

If later it’s no longer just a calendar agent but gradually a life assistant, planning may need further layering.

For example, the user says:

我希望三个月以后可以参加一次半程马拉松。

This is no longer calendar CRUD.

The system may need to break it into:

Goal

Plan

Tasks

Calendar Actions

For example:

完成半程马拉松

制定 12 周训练计划

每周训练 4 次

寻找用户空闲时间

创建具体日程

At this point:

Goal Planner

Task Planner

Calendar Agent

Tools

The Planner naturally evolves from a simple tool into a hierarchical planner.

But that’s for later.

I increasingly feel there’s an important principle in agent architecture:

Don’t build the entire final architecture up front — let complexity grow layer by layer as problems appear.


9. Re-examining “intent recognition”: maybe it isn’t a classifier at all

Reaching here and looking back at the original problem is interesting.

At first I wanted to solve:

How to improve intent-recognition accuracy?

So I kept adding:

Intent
Prompt
Rules
Examples
Few-shot
Bad Case

But now I increasingly feel:

For agent products, “intent recognition” maybe shouldn’t be just:

User Input

Intent Classifier

CREATE / QUERY / UPDATE / DELETE

A more reasonable structure might be:

Conversation Context

    Agent Reasoning

 ┌────────┼─────────┐
 ↓        ↓         ↓
Answer  Clarify   Action

              ┌────┴────┐
              ↓         ↓
            Simple    Complex
              ↓         ↓
            Tool      Planner

                      ReAct

                      Tools

Intent recognition went from an independent module to part of agent reasoning.

This is my biggest cognitive change from the recent calendar-agent work.


10. When bad cases never run out, maybe stop and look at the architecture

Making AI products, it’s easy to fall into a state:

A bad case appears, so keep changing the prompt.

This is useful, of course.

But if you find:

Fix one case, and a different phrasing breaks it again.

and this keeps happening, it’s worth stopping to ask one question:

Am I solving a model-capability problem, or using prompts to patch an architecture problem?

These are two completely different things.

My judgment standard now:

If a problem keeps needing more:

if the user says this……
if the last round was this……
if this field doesn't exist……
if there's a conflict……
if the tool fails……
if……

then it likely means:

A new architectural abstraction should appear here.

It might be:

Tool
Memory
Planner
State
Workflow
Error Taxonomy
Observability

rather than the 87th rule in the prompt.


In closing

My strongest feeling from recent agent work:

The really hard part of agent engineering isn’t making the LLM “smarter”, but designing an architecture where the system still works stably even when the LLM is occasionally not smart.

Conversation restores its context.

System prompt gives it boundaries.

Tools give it action capability.

Structured error gives it recovery.

Planner keeps its goal.

Observability helps the developer find systemic problems.

Memory lets it gradually understand this user.

What forms in the end isn’t really an “intent-recognition module”.

It’s:

Context

Reasoning

Planning

Action

Observation

Recovery

Memory

This is a complete agent loop.

So if you ask me again:

“How should a calendar agent’s intent recognition be done?”

My answer may become:

Don’t just do intent recognition.

Let the agent understand what the user is accomplishing, give it clear enough tools, constraints, planning, and error recovery, and let “intent” naturally show up in the action it picks next.

That’s probably closer to what an agent product should ultimately look like, than maintaining an ever-growing intent classifier.

Comments

Loading…
Comments are stored securely via GitHub and appear shortly after submitting.