Skip to main content

Overview

Planning is how an agent breaks a complex request into discrete, ordered, and trackable steps. Instead of letting the model juggle a multi-step goal entirely in free-form reasoning, AgentScope exposes a small set of built-in tools that let the agent maintain an explicit, structured task list — created, queried, and updated through normal tool calls. AgentScope ships four planning tools out of the box: All four are state-injected tools (is_state_injected = True): the agent runtime hands each call the live AgentState, and the tools read from / write to agent.state.tasks_context. That means the task list is scoped per agent and persists with the agent state.

Use Plan Tools

Equip the Tools

Instantiate the tools and register them on a Toolkit like any other built-in tool:
Each tool’s description already contains a detailed prompt describing when to call it, when to skip it, and how to interpret its output, so no additional system-prompt engineering is required. check_permissions() is hard-wired to ALLOW — the planning tools are pure in-memory state mutations and never trigger user prompts.

Task Lifecycle

A typical planning loop looks like this:
1

Capture the work

On a new instruction, the agent calls TaskCreate once per discrete step, providing a short imperative subject and a richer description. New tasks are appended in creation order; their id is a stable, monotonically increasing numeric string ("1", "2", …).
2

Inspect the queue

TaskList returns a compact one-line-per-task summary (id, status, subject, owner, blocked-by), which the agent uses to pick the next available task — typically the lowest-ID pending task with no unresolved blocked_by.
3

Claim and start

Before starting work, the agent calls TaskUpdate to set the task’s status to in_progress (and optionally an owner for multi-agent scenarios).
4

Get full context

TaskGet returns the full description, dependency edges, and metadata for a specific task — useful right before execution if the description is long.
5

Finish or re-plan

On completion, TaskUpdate flips the status to completed. If the agent uncovers new work, it loops back to TaskCreate; if a task becomes obsolete, it sets status deleted (a hard removal that also rewires the dependency edges of any tasks that referenced it).
The status workflow is intentionally linear:

Express Dependencies

Tasks expose two symmetric dependency edges:
  • blocks — the IDs of tasks that cannot start until this one is completed.
  • blocked_by — the IDs of tasks that must complete before this one can start.
TaskUpdate takes add_blocks and add_blocked_by arguments. Each one mutates both sides of the edge automatically, so the data stays consistent:
When a task is deleted, its ID is removed from every other task’s blocks and blocked_by lists, so the dependency graph remains valid.
TaskList annotates every task that still has unresolved blocked_by entries, and TaskGet returns the full edge list. The agent uses these hints to prefer unblocked work, but enforcement is advisory — nothing in the runtime prevents the model from working on a blocked task.

Storage

All task state lives on the agent itself, under agent.state.tasks_context. The relevant types are:
AgentState.tasks_context is a regular field on the agent.state model, which means:
  • It survives serialization. Saving agent.state captures the task list verbatim, and restoring the state restores the plan.
  • It is per-agent. Two agents do not share a task list by default; multi-agent coordination is the developer’s job.
  • It is mutable from outside the agent loop. Anything that can reach agent.state — middleware, application code, evaluators — can read and write tasks directly. The planning tools have no privileged access; they are simply a convenient LLM-facing surface over the same data structure.

Customize Tasks

Because tasks live on agent.state.tasks_context, developers can manage them programmatically without going through the LLM. This is useful for:
  • Seeding the agent with a pre-baked plan generated elsewhere (e.g. by another agent, a workflow engine, or static analysis).
  • Importing existing work items from an external tracker (Jira, GitHub issues, an internal task DB).
  • Migrating state across agent instances or restoring partially completed plans.
  • Evaluation of planning behavior, where the harness needs to inject ground-truth tasks before the agent reasons over them.
The example below seeds two dependent tasks before the agent’s first reply:
When mutating tasks_context directly, you are responsible for:
  • Unique, parseable IDs. TaskCreate derives the next ID by taking max(int(task.id) for task in tasks) + 1. Non-numeric IDs are ignored when computing the next ID, but they will not be revisited — assign numeric string IDs ("1", "2", …) to keep auto-generation working.
  • Bidirectional dependency edges. blocks and blocked_by must stay in sync. TaskUpdate does this automatically; manual edits do not.
  • Valid status values. Only pending, in_progress, and completed are valid for Task.state. deleted is an operation exposed by TaskUpdate, not a stored state — to drop a task by hand, simply remove it from the list (and clean up its edges).
You can also clear or replace the plan at any time:
The next agent turn will see an empty plan and start over.

Further Reading

  • Tool — the toolkit, the ToolBase interface, and how state-injected tools receive AgentState.
  • Agent — the agent lifecycle, including how AgentState is created, restored, and persisted.