Why Planning Uses Services While State and Assignments Use Topics
Why do submit_task and plan_route use services, while robot_states, task_assignments, and task_statuses use topics? This looks like a ROS 2 interface question, but the implementation raises a more useful distinction: which interactions require an explicit answer, and which should propagate state and events continuously?
The V1–V1.3 system includes task submission by external clients, route requests from the manager, assignments to robots, robot feedback, and task lifecycle events. All of these transfer data; they do not all have the same meaning.
The current objective
README.md, STATUS.md, and TECHNICAL_APPROACH.md describe a deliberately limited coordination chain: receive a transport task, select an available robot, obtain a graph route, send it for execution, and observe progress through completion. Each interaction needs a communication pattern matching its role. Otherwise a request that needs a definite answer becomes an ambiguous message exchange, or a natural stream of state becomes polling and blocking calls. Neither services nor topics are intrinsically more advanced.
Two communication roles
submit_task and plan_route are requests with explicit dependencies: an initiator asks, a recipient returns a result, and subsequent behavior depends on that result. The key question is whether this particular request received an answer.
robot_states, task_assignments, and task_statuses distribute state or events. Potentially several subscribers need to see each change. Their role is to make information flow through the ROS graph, rather than complete a single request/response transaction.
Why submit_task is a service
The external task entry point is:
1 | fleet_msgs/Task task |
A client needs to know whether the manager received the task, accepted it into the queue, and what explanation the system returned. Publishing a message alone does not establish that the manager is online, consumed the message, or accepted the task. This boundary needs an acknowledgment of acceptance.
Submission is also a discrete operation: a user submits a task and gets an immediate queuing decision. It is not a high-frequency telemetry stream. An RPC-style interface fits this interaction.
Crucially, accepted = true means that the manager admitted the task into its processing flow. It does not promise successful planning, immediate assignment, or eventual completion. Planner rejection, reservation blocking, waiting, execution, and completion belong to later lifecycle stages. The service acknowledges the entry transaction without claiming to settle the whole lifecycle.
Why plan_route is a service
The manager selects an idle robot, reads its current_waypoint, calls plan_route, and publishes TaskAssignment only after receiving a successful route. Failure can lead to a failed or waiting state. Planning therefore lies on the dispatch critical path.
The manager asks a specific question about a robot, pickup, and dropoff, and needs a bounded answer containing success, message, and route_waypoints. That answer immediately controls whether to dispatch, reject, or wait. Recreating this exchange with request and reply topics would require request identifiers, timeouts, response matching, and duplicate handling. That extra protocol is unnecessary here.
The current graph BFS and reservation check are short operations. They return a complete result and do not need long-running progress feedback or cancellation. An action could become useful for more expensive planning, but the present operation fits a service. This boundary also keeps responsibilities clear: the manager asks a planning question; the planner answers it. The manager does not search the graph, and the planner does not schedule tasks.
Why robot_states is a topic
Robot state should be published continuously, rather than only when somebody asks. The agent currently publishes once per second, including robot_id, pose, current_waypoint, status, battery_percent, and current_task_id. This is a time series describing where the robot is and what it is doing.
With services, the manager would have to poll each robot repeatedly. With a topic, each robot publishes and the manager caches the latest observation. Visualization, monitoring, debugging tools, and task dashboards can later subscribe to the same stream without introducing another polling loop.
Why task_assignments currently uses a topic
An assignment is a command, so considering a service or action is reasonable. The current design nevertheless has a practical reason for using a topic. The manager publishes:
1 | string robot_id |
Every agent can receive task_assignments, but only the agent whose robot_id matches processes the message. It is a targeted instruction carried on a shared topic. For the two-robot V1 demo, broadcasting and filtering locally keep the manager simple. Per-robot services would add endpoint management, availability handling, retry decisions, and coupling.
The execution model is limited: receive an assignment, retain the route, advance waypoints on a timer, and report progress through robot_states and task_statuses. The assignment topic itself does not provide acceptance acknowledgment or a complete lifecycle control protocol. Cancellation, preemption, detailed feedback, and explicit result confirmation would make actions increasingly appropriate. At this stage the topic distributes a route-bearing instruction; the feedback channels expose execution.
Why task_statuses is a topic
Task status is an event stream rather than periodic telemetry or a single request/response exchange. States such as queued, assigned, executing, waiting, completed, and failed tell observers what just changed. Verification scripts can observe the lifecycle, monitoring tools can subscribe, and the manager and agent can report progress with loose coupling.
A query service would answer a different question: what is the current snapshot when I ask? The topic reports a transition when it occurs. Both can be useful, but they serve different purposes.
What this division achieves
| Interface | Current role |
|---|---|
submit_task service |
Acknowledge external task acceptance |
plan_route service |
Answer the planning dependency before dispatch |
robot_states topic |
Broadcast continuous robot state |
task_assignments topic |
Distribute execution instructions |
task_statuses topic |
Expose lifecycle events |
The design provides explicit answers at important request boundaries and continuous observability during execution. That supports the current priorities: an explainable main flow, observable behavior, and clear module responsibilities.
These choices can evolve
Assignments may eventually require action semantics when cancellation, preemption, finer feedback, and confirmed execution results become necessary. Topics plus separate status feedback are sufficient for this stage, not necessarily the final interface.
The live task_statuses stream also lacks a dedicated historical query API. A late subscriber may miss previous transitions. Operational history would need persistence, a query service, or a separate history node.
Likewise, planning could outgrow a service if conflict resolution becomes a lengthy computation requiring cancellation or replanning. The current choice reflects the present problem size.
The project therefore uses services and topics according to interaction semantics: explicit answers for task intake and planning; ongoing distribution for state, assignments, and lifecycle events. This makes it possible to explain how tasks enter the system, how routes enable dispatch, and how execution and completion are observed and verified.
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 !