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:

ToolFunction
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 Read returns *filesystem.FileContent (containing a Content string field), 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 ValueDescription
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"

ParameterTypeRequiredDescription
subject
stringYesTask title (in imperative form)
description
stringYesDetailed task description, including context and acceptance criteria
activeForm
stringNoActive form text (e.g., "Running tests"), displayed to the user when status is in_progress
metadata
objectNoCustom 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"

ParameterTypeRequiredDescription
taskId
stringYesTask ID (numeric string)

Returns the complete task information: subject, description, status, blocks, blockedBy, owner.

TaskUpdate

Tool name constant: TaskUpdateToolName = "TaskUpdate"

ParameterTypeRequiredDescription
taskId
stringYesTask ID
subject
stringNoNew title
description
stringNoNew description
activeForm
stringNoNew active form text
status
stringNoNew status, enum:
pending
/
in_progress
/
completed
/
deleted
addBlocks
[]stringNoAdd task IDs that are blocked by the current task (bidirectional write)
addBlockedBy
[]stringNoAdd task IDs that block the current task (bidirectional write)
owner
stringNoName of the responsible agent
metadata
objectNoMerged 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

MechanismDescription
ID AllocationThe
.highwatermark
file stores the current maximum ID, incremented by 1 on creation
Concurrency SafetyAll four tools share the same
sync.Mutex
, serializing execution within a single middleware instance
File FormatEach task is stored in a
{id}.json
file, serialized using
sonic
Auto CleanupAfter TaskUpdate marks a task as completed, it checks β€” if all tasks are completed, they are batch deleted
ID ValidationNumeric-only regex
^\d+$
Cascading DeleteWhen 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.