agent_client_protocol_schema/v1/plan.rs
1//! Execution plans for complex tasks that require multiple steps.
2//!
3//! Plans are strategies that agents share with clients through session updates,
4//! providing real-time visibility into their thinking and progress.
5//!
6//! See: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
7
8#[cfg(feature = "unstable_plan_operations")]
9use std::sync::Arc;
10
11#[cfg(feature = "unstable_plan_operations")]
12use derive_more::{Display, From};
13use serde::{Deserialize, Serialize};
14use serde_with::{DefaultOnError, VecSkipError, serde_as, skip_serializing_none};
15
16use crate::{IntoOption, SkipListener};
17
18use super::Meta;
19
20/// An execution plan for accomplishing complex tasks.
21///
22/// Plans consist of multiple entries representing individual tasks or goals.
23/// Agents report plans to clients to provide visibility into their execution strategy.
24/// Plans can evolve during execution as the agent discovers new requirements or completes tasks.
25///
26/// See protocol docs: [Agent Plan](https://agentclientprotocol.com/protocol/agent-plan)
27#[serde_as]
28#[skip_serializing_none]
29#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
30#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32#[non_exhaustive]
33pub struct Plan {
34 /// The list of tasks to be accomplished.
35 ///
36 /// When updating a plan, the agent must send a complete list of all entries
37 /// with their current status. The client replaces the entire plan with each update.
38 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
39 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
40 pub entries: Vec<PlanEntry>,
41 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
42 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
43 /// these keys.
44 ///
45 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
46 #[serde_as(deserialize_as = "DefaultOnError")]
47 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
48 #[serde(default)]
49 #[serde(rename = "_meta")]
50 pub meta: Option<Meta>,
51}
52
53impl Plan {
54 /// Builds [`Plan`] with the required fields set; optional fields start unset or empty.
55 #[must_use]
56 pub fn new(entries: Vec<PlanEntry>) -> Self {
57 Self {
58 entries,
59 meta: None,
60 }
61 }
62
63 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
64 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
65 /// these keys.
66 ///
67 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
68 #[must_use]
69 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
70 self.meta = meta.into_option();
71 self
72 }
73}
74
75/// **UNSTABLE**
76///
77/// This capability is not part of the spec yet, and may be removed or changed at any point.
78///
79/// Unique identifier for a plan within a session.
80#[cfg(feature = "unstable_plan_operations")]
81#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
82#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash, Display, From)]
83#[serde(transparent)]
84#[from(Arc<str>, String, &'static str)]
85#[non_exhaustive]
86pub struct PlanId(pub Arc<str>);
87
88#[cfg(feature = "unstable_plan_operations")]
89impl PlanId {
90 /// Wraps a protocol string as a typed [`PlanId`].
91 #[must_use]
92 pub fn new(id: impl Into<Arc<str>>) -> Self {
93 Self(id.into())
94 }
95}
96
97/// **UNSTABLE**
98///
99/// This capability is not part of the spec yet, and may be removed or changed at any point.
100///
101/// A content update for a plan identified by ID.
102#[cfg(feature = "unstable_plan_operations")]
103#[serde_as]
104#[skip_serializing_none]
105#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
106#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
107#[serde(rename_all = "camelCase")]
108#[non_exhaustive]
109pub struct PlanUpdate {
110 /// The updated plan content.
111 pub plan: PlanUpdateContent,
112 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
113 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
114 /// these keys.
115 ///
116 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
117 #[serde_as(deserialize_as = "DefaultOnError")]
118 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
119 #[serde(default)]
120 #[serde(rename = "_meta")]
121 pub meta: Option<Meta>,
122}
123
124#[cfg(feature = "unstable_plan_operations")]
125impl PlanUpdate {
126 /// Builds [`PlanUpdate`] with the required fields set; optional fields start unset or empty.
127 #[must_use]
128 pub fn new(plan: PlanUpdateContent) -> Self {
129 Self { plan, meta: None }
130 }
131
132 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
133 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
134 /// these keys.
135 ///
136 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
137 #[must_use]
138 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
139 self.meta = meta.into_option();
140 self
141 }
142}
143
144/// **UNSTABLE**
145///
146/// This capability is not part of the spec yet, and may be removed or changed at any point.
147///
148/// Updated content for a plan.
149#[cfg(feature = "unstable_plan_operations")]
150#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
151#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
152#[serde(tag = "type", rename_all = "snake_case")]
153#[cfg_attr(feature = "schemars", schemars(extend("discriminator" = {"propertyName": "type"})))]
154#[non_exhaustive]
155pub enum PlanUpdateContent {
156 /// Structured plan entries.
157 Items(PlanItems),
158 /// A URI pointing to a file containing the plan.
159 File(PlanFile),
160 /// Raw markdown content for the plan.
161 Markdown(PlanMarkdown),
162}
163
164#[cfg(feature = "unstable_plan_operations")]
165impl PlanUpdateContent {
166 /// Builds a plan update that replaces the itemized entries for a plan.
167 #[must_use]
168 pub fn items(plan_id: impl Into<PlanId>, entries: Vec<PlanEntry>) -> Self {
169 Self::Items(PlanItems::new(plan_id, entries))
170 }
171
172 /// Builds a plan update that points clients at an external plan file URI.
173 #[must_use]
174 pub fn file(plan_id: impl Into<PlanId>, uri: impl Into<String>) -> Self {
175 Self::File(PlanFile::new(plan_id, uri))
176 }
177
178 /// Builds a plan update whose plan content is inline Markdown.
179 #[must_use]
180 pub fn markdown(plan_id: impl Into<PlanId>, content: impl Into<String>) -> Self {
181 Self::Markdown(PlanMarkdown::new(plan_id, content))
182 }
183}
184
185/// **UNSTABLE**
186///
187/// This capability is not part of the spec yet, and may be removed or changed at any point.
188///
189/// A plan represented as structured entries.
190#[cfg(feature = "unstable_plan_operations")]
191#[serde_as]
192#[skip_serializing_none]
193#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
194#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
195#[serde(rename_all = "camelCase")]
196#[non_exhaustive]
197pub struct PlanItems {
198 /// The plan ID to update.
199 pub plan_id: PlanId,
200 /// The list of tasks to be accomplished.
201 ///
202 /// When updating an item-based plan, the agent must send a complete list of all entries
203 /// with their current status. The client replaces that plan with each update.
204 #[serde_as(deserialize_as = "DefaultOnError<VecSkipError<_, SkipListener>>")]
205 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true, "x-deserialize-skip-invalid-items" = true)))]
206 pub entries: Vec<PlanEntry>,
207 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
208 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
209 /// these keys.
210 ///
211 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
212 #[serde_as(deserialize_as = "DefaultOnError")]
213 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
214 #[serde(default)]
215 #[serde(rename = "_meta")]
216 pub meta: Option<Meta>,
217}
218
219#[cfg(feature = "unstable_plan_operations")]
220impl PlanItems {
221 /// Builds [`PlanItems`] with the required fields set; optional fields start unset or empty.
222 #[must_use]
223 pub fn new(plan_id: impl Into<PlanId>, entries: Vec<PlanEntry>) -> Self {
224 Self {
225 plan_id: plan_id.into(),
226 entries,
227 meta: None,
228 }
229 }
230
231 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
232 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
233 /// these keys.
234 ///
235 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
236 #[must_use]
237 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
238 self.meta = meta.into_option();
239 self
240 }
241}
242
243/// **UNSTABLE**
244///
245/// This capability is not part of the spec yet, and may be removed or changed at any point.
246///
247/// A plan represented by a file URI.
248#[cfg(feature = "unstable_plan_operations")]
249#[serde_as]
250#[skip_serializing_none]
251#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
253#[serde(rename_all = "camelCase")]
254#[non_exhaustive]
255pub struct PlanFile {
256 /// The plan ID to update.
257 pub plan_id: PlanId,
258 /// The URI of the file containing the plan.
259 pub uri: String,
260 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
261 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
262 /// these keys.
263 ///
264 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
265 #[serde_as(deserialize_as = "DefaultOnError")]
266 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
267 #[serde(default)]
268 #[serde(rename = "_meta")]
269 pub meta: Option<Meta>,
270}
271
272#[cfg(feature = "unstable_plan_operations")]
273impl PlanFile {
274 /// Builds [`PlanFile`] with the required fields set; optional fields start unset or empty.
275 #[must_use]
276 pub fn new(plan_id: impl Into<PlanId>, uri: impl Into<String>) -> Self {
277 Self {
278 plan_id: plan_id.into(),
279 uri: uri.into(),
280 meta: None,
281 }
282 }
283
284 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
285 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
286 /// these keys.
287 ///
288 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
289 #[must_use]
290 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
291 self.meta = meta.into_option();
292 self
293 }
294}
295
296/// **UNSTABLE**
297///
298/// This capability is not part of the spec yet, and may be removed or changed at any point.
299///
300/// A plan represented as raw markdown content.
301#[cfg(feature = "unstable_plan_operations")]
302#[serde_as]
303#[skip_serializing_none]
304#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
305#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
306#[serde(rename_all = "camelCase")]
307#[non_exhaustive]
308pub struct PlanMarkdown {
309 /// The plan ID to update.
310 pub plan_id: PlanId,
311 /// Markdown content for the plan.
312 pub content: String,
313 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
314 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
315 /// these keys.
316 ///
317 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
318 #[serde_as(deserialize_as = "DefaultOnError")]
319 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
320 #[serde(default)]
321 #[serde(rename = "_meta")]
322 pub meta: Option<Meta>,
323}
324
325#[cfg(feature = "unstable_plan_operations")]
326impl PlanMarkdown {
327 /// Builds [`PlanMarkdown`] with the required fields set; optional fields start unset or empty.
328 #[must_use]
329 pub fn new(plan_id: impl Into<PlanId>, content: impl Into<String>) -> Self {
330 Self {
331 plan_id: plan_id.into(),
332 content: content.into(),
333 meta: None,
334 }
335 }
336
337 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
338 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
339 /// these keys.
340 ///
341 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
342 #[must_use]
343 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
344 self.meta = meta.into_option();
345 self
346 }
347}
348
349/// **UNSTABLE**
350///
351/// This capability is not part of the spec yet, and may be removed or changed at any point.
352///
353/// Removal notice for a plan identified by ID.
354#[cfg(feature = "unstable_plan_operations")]
355#[serde_as]
356#[skip_serializing_none]
357#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
358#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
359#[serde(rename_all = "camelCase")]
360#[non_exhaustive]
361pub struct PlanRemoved {
362 /// The plan ID to remove.
363 pub plan_id: PlanId,
364 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
365 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
366 /// these keys.
367 ///
368 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
369 #[serde_as(deserialize_as = "DefaultOnError")]
370 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
371 #[serde(default)]
372 #[serde(rename = "_meta")]
373 pub meta: Option<Meta>,
374}
375
376#[cfg(feature = "unstable_plan_operations")]
377impl PlanRemoved {
378 /// Builds [`PlanRemoved`] with the required fields set; optional fields start unset or empty.
379 #[must_use]
380 pub fn new(plan_id: impl Into<PlanId>) -> Self {
381 Self {
382 plan_id: plan_id.into(),
383 meta: None,
384 }
385 }
386
387 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
388 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
389 /// these keys.
390 ///
391 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
392 #[must_use]
393 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
394 self.meta = meta.into_option();
395 self
396 }
397}
398
399/// **UNSTABLE**
400///
401/// This capability is not part of the spec yet, and may be removed or changed at any point.
402///
403/// Capabilities for receiving `plan_update` and `plan_removed` session updates.
404#[cfg(feature = "unstable_plan_operations")]
405#[serde_as]
406#[skip_serializing_none]
407#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
408#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
409#[serde(rename_all = "camelCase")]
410#[non_exhaustive]
411pub struct PlanCapabilities {
412 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
413 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
414 /// these keys.
415 ///
416 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
417 #[serde_as(deserialize_as = "DefaultOnError")]
418 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
419 #[serde(default)]
420 #[serde(rename = "_meta")]
421 pub meta: Option<Meta>,
422}
423
424#[cfg(feature = "unstable_plan_operations")]
425impl PlanCapabilities {
426 /// Builds an empty [`PlanCapabilities`]; use builder methods to advertise supported sub-capabilities.
427 #[must_use]
428 pub fn new() -> Self {
429 Self::default()
430 }
431
432 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
433 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
434 /// these keys.
435 ///
436 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
437 #[must_use]
438 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
439 self.meta = meta.into_option();
440 self
441 }
442}
443
444/// A single entry in the execution plan.
445///
446/// Represents a task or goal that the assistant intends to accomplish
447/// as part of fulfilling the user's request.
448/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
449#[serde_as]
450#[skip_serializing_none]
451#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
452#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
453#[serde(rename_all = "camelCase")]
454#[non_exhaustive]
455pub struct PlanEntry {
456 /// Human-readable description of what this task aims to accomplish.
457 pub content: String,
458 /// The relative importance of this task.
459 /// Used to indicate which tasks are most critical to the overall goal.
460 pub priority: PlanEntryPriority,
461 /// Current execution status of this task.
462 pub status: PlanEntryStatus,
463 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
464 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
465 /// these keys.
466 ///
467 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
468 #[serde_as(deserialize_as = "DefaultOnError")]
469 #[cfg_attr(feature = "schemars", schemars(extend("x-deserialize-default-on-error" = true)))]
470 #[serde(default)]
471 #[serde(rename = "_meta")]
472 pub meta: Option<Meta>,
473}
474
475impl PlanEntry {
476 /// Builds [`PlanEntry`] with the required fields set; optional fields start unset or empty.
477 #[must_use]
478 pub fn new(
479 content: impl Into<String>,
480 priority: PlanEntryPriority,
481 status: PlanEntryStatus,
482 ) -> Self {
483 Self {
484 content: content.into(),
485 priority,
486 status,
487 meta: None,
488 }
489 }
490
491 /// The _meta property is reserved by ACP to allow clients and agents to attach additional
492 /// metadata to their interactions. Implementations MUST NOT make assumptions about values at
493 /// these keys.
494 ///
495 /// See protocol docs: [Extensibility](https://agentclientprotocol.com/protocol/extensibility)
496 #[must_use]
497 pub fn meta(mut self, meta: impl IntoOption<Meta>) -> Self {
498 self.meta = meta.into_option();
499 self
500 }
501}
502
503/// Priority levels for plan entries.
504///
505/// Used to indicate the relative importance or urgency of different
506/// tasks in the execution plan.
507/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
508#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
509#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
510#[serde(rename_all = "snake_case")]
511#[non_exhaustive]
512pub enum PlanEntryPriority {
513 /// High priority task - critical to the overall goal.
514 High,
515 /// Medium priority task - important but not critical.
516 Medium,
517 /// Low priority task - nice to have but not essential.
518 Low,
519}
520
521/// Status of a plan entry in the execution flow.
522///
523/// Tracks the lifecycle of each task from planning through completion.
524/// See protocol docs: [Plan Entries](https://agentclientprotocol.com/protocol/agent-plan#plan-entries)
525#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
526#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)]
527#[serde(rename_all = "snake_case")]
528#[non_exhaustive]
529pub enum PlanEntryStatus {
530 /// The task has not started yet.
531 Pending,
532 /// The task is currently being worked on.
533 InProgress,
534 /// The task has been successfully completed.
535 Completed,
536}