fleet_manager: Task Queue, Planner Dependency, and Assignment State Retention

ROS2

Posted by Bruce Lee on 2025-03-10

fleet_manager: Task Queue, Planner Dependency, and Assignment State Retention

At this stage, fleet_manager is not an advanced scheduler. Its role is still
the most important middle link in the V1 coordination loop:

  1. accept tasks;
  2. observe robot state;
  3. select an available robot;
  4. request a route from the planner;
  5. publish an assignment;
  6. wait for execution and completion feedback.

If robot_agent simulates execution and path_planner produces routes, then
fleet_manager turns a static task request into a dynamic dispatch process
that can flow through the system.

This article focuses on three questions:

  1. how tasks enter the pending queue;
  2. why the manager currently depends on the planner;
  3. why the manager must actively retain assignment state instead of blindly
    trusting every returned topic message.

Current Goal

The goal is not optimal dispatch. The goal is to make this chain real:

SubmitTask -> pending queue -> select idle robot -> request PlanRoute -> publish TaskAssignment -> robot executes -> completed feedback

The central requirement is that intermediate state must not disappear.

If any link is missing, the system can look alive while still being broken:

  1. the task is registered but never dispatched;
  2. the manager believes it assigned the task, but the robot never receives it;
  3. the robot finishes, but the manager never releases the resource.

So the current focus is state flow, not configuration alone.


Interface-Level View

fleet_manager works around three core data contracts.

Task

1
2
3
4
string task_id
string pickup_waypoint
string dropoff_waypoint
uint8 priority

This is a deliberately restrained task model. It is not a general workflow
object. It is a transport task with:

  • an ID;
  • a pickup waypoint;
  • a dropoff waypoint;
  • a priority field reserved for later scheduling policy.

The interface already leaves room for priority-aware scheduling, but the
implementation does not prematurely introduce that policy. That is a stable
design choice: keep the field, but keep the dispatch behavior minimal until the
main loop is correct.

RobotState

1
2
3
4
5
6
string robot_id
geometry_msgs/Pose pose
string current_waypoint
string status
float32 battery_percent
string current_task_id

For the current manager, the most meaningful fields are:

  • status;
  • current_waypoint;
  • current_task_id.

The manager is not primarily asking for the robot’s exact continuous position.
It is asking:

  1. is the robot idle?
  2. which waypoint is it currently at?
  3. does it already have an active task?

RobotState is therefore both a state broadcast and the manager’s input cache
for dispatch decisions.

TaskAssignment

1
2
3
string robot_id
fleet_msgs/Task task
string[] route_waypoints

This message is important because the manager does not simply tell the robot
“do task_x.” It sends both the task and the discrete execution route.

In the current system, a manager output is not a pure intent. It is an
execution command with route semantics attached. That places the planner on the
critical dispatch path.


How Tasks Enter the Pending Queue

The manager receives tasks through the submit_task service:

1
2
3
4
fleet_msgs/Task task
---
bool accepted
string message

The implementation is intentionally direct. It pushes request->task into
pending_tasks_, returns accepted = true, and reports task queued.

That simplicity is useful.

First, task intake is decoupled from task execution. The client is not promised
that a robot will be assigned immediately. It is promised that the system has
accepted the task into a pending state.

Second, the manager keeps control of dispatch timing. Assignment does not
happen inside the service callback. It happens later in assign_pending_tasks()
on a timer. That gives the manager one consistent place to inspect robot state,
planner availability, current waypoint validity, and pending work.

The current pending queue is still a simple FIFO-like container. Although
Task contains priority, priority has not yet become a real ordering or
preemption mechanism.


Why the Manager Cannot Skip the Planner

Before publishing an assignment, assign_pending_tasks() follows this shape:

  1. return if there is no pending task;
  2. return if a planning request is already in flight;
  3. select an idle robot;
  4. check that the robot has a current_waypoint;
  5. check whether the plan_route service is available;
  6. build a PlanRoute::Request;
  7. call the planner asynchronously;
  8. publish the assignment only after receiving a route.

This shows a key property of V1: dispatch logically depends on successful
planning.

That is because the robot consumes TaskAssignment, and TaskAssignment
contains route_waypoints. If the manager does not obtain a route first, it
has only two bad options:

  1. publish an incomplete assignment;
  2. move planning logic back into the manager.

The first option makes the agent protocol ambiguous. The second breaks the
current separation of responsibilities. Keeping planning as a service dependency
is the right V1 decision.

It also means the manager is not just a queue owner. It is a dispatcher with
preconditions:

  • the planner must be online;
  • the planner must find a path in the graph;
  • the robot must know its current waypoint.

If any of those preconditions is missing, delaying assignment is better than
publishing a semantically incomplete command.


Why planning_request_in_flight_ Matters

planning_request_in_flight_ is a small boolean with an important role. It
prevents the manager from repeatedly sending planning requests for the same
queue head while the previous request has not returned.

Without this explicit transition state, a timer-driven manager can create:

  1. duplicate planning calls;
  2. duplicate assignment risk;
  3. mismatches between queue state and planner callback results.

Conceptually, this boolean models: “the queue head is waiting for a planner
result.” It is the kind of intermediate state that a finite-state design would
make explicit. Without it, the implementation can still compile, but its
boundaries become vague.


Why First-Idle Dispatch Is Acceptable Now

select_idle_robot() is currently simple:

  1. iterate over robot_states_;
  2. return the first robot with status == "idle";
  3. return empty if none is available.

As a scheduling algorithm, this is weak. As a V1 validation strategy, it is
stable.

The system first needs to prove that:

  • robot state is reported continuously;
  • the manager caches that state correctly;
  • tasks are actually dispatched;
  • assigned robots enter execution;
  • completed robots become available again.

Distance cost, battery cost, priority competition, and workload balancing are
all real future concerns. Introducing them before the main lifecycle is stable
would only increase the number of failure sources.


Assignment State Retention

The most interesting part of fleet_manager is not queue insertion or planner
calling. It is how the manager corrects incoming robot state after assignment.

The logic is roughly:

  1. if the previous record for this robot has a current_task_id;
  2. and the new message says status == idle with an empty current_task_id;
  3. then the manager does not immediately trust the new message and keeps the
    assignment-related state.

There is also the completion case:

  1. if the new message is completed;
  2. and its current_task_id matches the task recorded by the manager;
  3. then the manager rewrites its internal cache to idle with an empty task ID.

This behavior deserves attention.

Why the Manager Cannot Trust Every Topic Update

Topic propagation is not atomic. After the manager publishes an assignment, the
agent does not switch to executing and report back in the same instant. There
is a timing gap.

If the manager receives an old periodic idle update during that gap and
blindly accepts it, the robot may appear available immediately after being
assigned.

That can cause:

  1. duplicate assignment to the same robot;
  2. corrupted task ownership;
  3. an assignment lifecycle broken by topic timing.

The manager’s retention logic is therefore not cosmetic. It is a consistency
guard.

What This Logic Compensates For

Dispatch state and execution state are produced by different nodes. They have
natural propagation latency.

The manager knows: “I just assigned task_y to robot_x.”

The agent knows: “I will publish execution state on the next cycle.”

If no node remembers that transitional truth, the system can contradict itself
inside a short time window. The manager is currently responsible for preserving
that truth.

This is more than a cache. It is the beginning of an explicit assignment
lifecycle model.


V1 Boundaries

The manager now connects the minimal loop, but its limits are still clear.

First, priority is not yet used as a real scheduling input.

Second, there is no conflict detection or reservation. The manager only asks
“who is idle?” It does not yet ask “will this route conflict with another
robot?”

Third, robot selection has no cost model. It does not consider proximity to
pickup, battery suitability, or expected completion time.

Fourth, planner failure handling is still simple. A failed route mostly leaves
the task pending rather than triggering richer recovery behavior.

Fifth, assignment retention is still a local protection mechanism. If the task
lifecycle becomes more complex, this should grow into a formal state model.


Summary

The value of the current fleet_manager is not that it implements clever
optimization. Its value is that it puts task intake, planner dependency,
assignment publication, and state retention into one controllable node. That is
what turns the V1 main path into a real system chain.


If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. All the images used in the blog are my original works or AI works, if you want to take it,don't hesitate. Thank you !