PlanTask
π‘ This middleware was introduced in v0.8.0. Package path:
github.com/cloudwego/eino/adk/middlewares/plantask
Overview
plantask is a task management middleware that injects four tools into the Agent via the BeforeAgent hook, providing structured task planning capabilities:
| Tool | Function |
TaskCreate | Create a task |
TaskGet | Get details of a single task |
TaskUpdate | Update task status/fields, set dependencies, delete tasks |
TaskList | List summaries of all tasks |
Core use case: Break down complex requests into trackable sub-tasks, manage dependencies between tasks, and show execution progress to the user.
Architecture
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Agent β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β BeforeAgent: Inject task tools (with sync.Mutex for concurrency) β β
β β - TaskCreate β β
β β - TaskGet β β
β β - TaskUpdate β β
β β - TaskList β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Backend β
β β
β Storage structure: β
β baseDir/ β
β βββ .highwatermark # Maximum allocated ID (plain-text number) β
β βββ 1.json # Task #1 β
β βββ 2.json # Task #2 β
β βββ ... β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
API
Constructor
// Generic version, supports *schema.Message and *schema.AgenticMessage
func NewTyped[M adk.MessageType](ctx context.Context, config *Config) (adk.TypedChatModelAgentMiddleware[M], error)
// Non-generic version, equivalent to NewTyped[*schema.Message]
func New(ctx context.Context, config *Config) (adk.ChatModelAgentMiddleware, error)
Config
type Config struct {
Backend Backend // Storage backend, required
BaseDir string // Task file storage directory, required
}
π‘ The Backend should be isolated at the session level β different sessions should correspond to different Backend instances (i.e., different task lists).
Backend Interface
Backend is defined within the plantask package and is a minimal subset of filesystem.Backend, retaining only the four methods needed for task storage:
type Backend interface {
LsInfo(ctx context.Context, req *LsInfoRequest) ([]FileInfo, error)
Read(ctx context.Context, req *ReadRequest) (*filesystem.FileContent, error)
Write(ctx context.Context, req *WriteRequest) error
Delete(ctx context.Context, req *DeleteRequest) error
}
Type alias relationships:
type FileInfo = filesystem.FileInfo // Path, IsDir, Size, ModifiedAt
type LsInfoRequest = filesystem.LsInfoRequest // Path string
type ReadRequest = filesystem.ReadRequest // FilePath, Offset, Limit
type WriteRequest = filesystem.WriteRequest // FilePath, Content string
// DeleteRequest is custom to the plantask package (not present in the filesystem package)
type DeleteRequest struct {
FilePath string
}
π‘ Note that
Readreturns*filesystem.FileContent(containing aContent stringfield), not a raw string. Import path:github.com/cloudwego/eino/adk/filesystem.
Task Structure
type task struct {
ID string `json:"id"`
Subject string `json:"subject"`
Description string `json:"description"`
Status string `json:"status"`
Blocks []string `json:"blocks"`
BlockedBy []string `json:"blockedBy"`
ActiveForm string `json:"activeForm,omitempty"`
Owner string `json:"owner,omitempty"`
Metadata map[string]any `json:"metadata,omitempty"`
}
Status
| Status Value | Description |
pending | Pending (default on creation) |
in_progress | In progress |
completed | Completed |
deleted | Deleted (physically deletes the JSON file and removes the ID from other tasks' dependency lists) |
Status transitions: pending β in_progress β completed; any status can be directly set to deleted.
Tool Parameters
TaskCreate
Tool name constant: TaskCreateToolName = "TaskCreate"
| Parameter | Type | Required | Description |
subject | string | Yes | Task title (in imperative form) |
description | string | Yes | Detailed task description, including context and acceptance criteria |
activeForm | string | No | Active form text (e.g., "Running tests"), displayed to the user when status is in_progress |
metadata | object | No | Custom key-value pairs |
After creation, the task ID auto-increments (based on the .highwatermark file) and the initial status is pending.
TaskGet
Tool name constant: TaskGetToolName = "TaskGet"
| Parameter | Type | Required | Description |
taskId | string | Yes | Task ID (numeric string) |
Returns the complete task information: subject, description, status, blocks, blockedBy, owner.
TaskUpdate
Tool name constant: TaskUpdateToolName = "TaskUpdate"
| Parameter | Type | Required | Description |
taskId | string | Yes | Task ID |
subject | string | No | New title |
description | string | No | New description |
activeForm | string | No | New active form text |
status | string | No | New status, enum: pending/ in_progress/ completed/ deleted |
addBlocks | []string | No | Add task IDs that are blocked by the current task (bidirectional write) |
addBlockedBy | []string | No | Add task IDs that block the current task (bidirectional write) |
owner | string | No | Name of the responsible agent |
metadata | object | No | Merged into existing metadata; setting a key to null deletes that key |
Key behaviors:
status: "deleted"physically deletes the task file and removes the ID from all other tasks’ blocks/blockedBy lists- Circular dependency detection is performed when adding dependencies; an error is raised if a cycle is formed
- When all tasks are completed, all task files are automatically deleted (cleanup mechanism)
TaskList
Tool name constant: TaskListToolName = "TaskList"
No parameters. Returns a summary list of all tasks (sorted by ID), with each entry formatted as:
#ID [status] subject [owner: xxx] [blocked by #x, #y]
Usage Example
ctx := context.Background()
// The Backend should be isolated at the session level
middleware, err := plantask.New(ctx, &plantask.Config{
Backend: myBackend,
BaseDir: "/tasks",
})
if err != nil {
return err
}
agent, err := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Model: myModel,
Handlers: []adk.ChatModelAgentMiddleware{middleware},
})
Typical Flow
1. Receive a complex task
β
βΌ
2. TaskCreate to create multiple sub-tasks
- #1: Analyze requirements
- #2: Implement code
- #3: Write tests
β
βΌ
3. TaskUpdate to set dependencies
- #2 addBlockedBy: ["1"]
- #3 addBlockedBy: ["2"]
β
βΌ
4. TaskList to view available tasks
β
βΌ
5. TaskUpdate #1 β in_progress
β
βΌ
6. After completion, TaskUpdate #1 β completed
β
βΌ
7. Loop 4-6 until all completed
β
βΌ
8. All completed β automatically clean up all files
Dependency Management
- blocks: “Once I complete, these tasks can start”
- blockedBy: “Once these tasks complete, I can start”
Dependency writes are bidirectional: executing addBlocks: ["2"] on Task A will simultaneously add A’s ID to Task #2’s blockedBy.
Task #1 (blocks: ["2"]) βββββΊ Task #2 (blockedBy: ["1"])
#2 can only start after #1 completes
Circular dependency detection is implemented via DFS reachability:
#1 blocks #2
#2 blocks #1 β Error: would create a cyclic dependency
Implementation Details
| Mechanism | Description |
| ID Allocation | The .highwatermarkfile stores the current maximum ID, incremented by 1 on creation |
| Concurrency Safety | All four tools share the same sync.Mutex, serializing execution within a single middleware instance |
| File Format | Each task is stored in a {id}.json file, serialized using sonic |
| Auto Cleanup | After TaskUpdate marks a task as completed, it checks β if all tasks are completed, they are batch deleted |
| ID Validation | Numeric-only regex ^\d+$ |
| Cascading Delete | When a task is deleted, all task files are traversed to remove references to that ID |
Multi-language Support
Tool descriptions support Chinese and English, switchable via the global setting:
// Use Chinese descriptions
adk.SetLanguage(adk.LanguageChinese)
// Use English descriptions (default)
adk.SetLanguage(adk.LanguageEnglish)
This setting affects all ADK built-in prompts and tool descriptions.