mentra/agent/
task_state.rs1use std::borrow::Cow;
2
3use crate::error::RuntimeError;
4use crate::runtime::{
5 TaskBoard, TaskStateSnapshot,
6 task::{TASK_REMINDER_TEXT, TaskAccess, TaskIntrinsicTool, has_unfinished_tasks},
7};
8
9use super::Agent;
10
11impl Agent {
12 pub fn task_board(&self) -> TaskBoard {
19 TaskBoard::agent(
20 self.runtime.clone(),
21 self.config.task.tasks_dir.clone(),
22 self.name.clone(),
23 self.teammate_identity.is_some(),
24 )
25 }
26
27 pub(crate) fn effective_system_prompt(&self) -> Option<Cow<'_, str>> {
28 let mut sections = Vec::new();
29
30 if self.rounds_since_task >= self.config.task.reminder_threshold
31 && has_unfinished_tasks(&self.tasks)
32 {
33 sections.push(TASK_REMINDER_TEXT.to_string());
34 }
35
36 if let Some(system) = &self.config.system {
37 sections.push(system.clone());
38 }
39
40 if let Some(skills) = self.runtime.skill_descriptions() {
41 sections.push(skills);
42 }
43
44 if sections.is_empty() {
45 None
46 } else {
47 Some(Cow::Owned(sections.join("\n\n")))
48 }
49 }
50
51 pub(crate) fn note_round_without_task(&mut self) {
52 if has_unfinished_tasks(&self.tasks) {
53 self.rounds_since_task += 1;
54 }
55 }
56
57 pub(crate) fn record_task_activity(&mut self) {
58 self.rounds_since_task = 0;
59 }
60
61 pub(crate) fn refresh_tasks_from_disk(&mut self) -> Result<(), RuntimeError> {
62 let tasks = self
63 .runtime
64 .store()
65 .load_tasks(self.config.task.tasks_dir.as_path())?;
66 self.tasks = tasks;
67 let tasks = self.tasks.clone();
68 self.mutate_snapshot(|snapshot| {
69 snapshot.tasks = tasks;
70 });
71 Ok(())
72 }
73
74 pub(crate) fn task_access(&self) -> TaskAccess<'_> {
75 match &self.teammate_identity {
76 Some(_) => TaskAccess::Teammate(self.name.as_str()),
77 None => TaskAccess::Lead,
78 }
79 }
80
81 pub(crate) fn try_claim_ready_task(
82 &mut self,
83 ) -> Result<Option<crate::runtime::TaskItem>, RuntimeError> {
84 self.refresh_tasks_from_disk()?;
85 if self.owns_unfinished_tasks() {
86 return Ok(None);
87 }
88
89 match self.execute_task_mutation(&TaskIntrinsicTool::Claim, serde_json::json!({})) {
90 Ok(content) => {
91 self.refresh_tasks_from_disk()?;
92 serde_json::from_str::<crate::runtime::TaskItem>(&content)
93 .map(Some)
94 .map_err(RuntimeError::FailedToSerializeTasks)
95 }
96 Err(error) if error == "No ready unowned tasks are available to claim" => Ok(None),
97 Err(error) => Err(RuntimeError::InvalidTask(error)),
98 }
99 }
100
101 pub(crate) fn execute_task_mutation(
102 &self,
103 tool: &TaskIntrinsicTool,
104 input: serde_json::Value,
105 ) -> Result<String, String> {
106 self.runtime.execute_task_mutation(
107 tool,
108 input,
109 self.config.task.tasks_dir.as_path(),
110 self.task_access(),
111 )
112 }
113
114 pub(super) fn capture_task_disk_state(&self) -> Result<TaskStateSnapshot, RuntimeError> {
115 self.runtime
116 .store()
117 .capture_tasks(self.config.task.tasks_dir.as_path())
118 }
119
120 fn owns_unfinished_tasks(&self) -> bool {
121 self.tasks.iter().any(|task| {
122 task.owner == self.name && !matches!(task.status, crate::runtime::TaskStatus::Completed)
123 })
124 }
125
126 pub(super) fn restore_task_state(
127 &mut self,
128 tasks: Vec<crate::runtime::TaskItem>,
129 rounds_since_task: usize,
130 disk_state: &TaskStateSnapshot,
131 ) -> Result<(), RuntimeError> {
132 self.runtime
133 .store()
134 .restore_tasks(self.config.task.tasks_dir.as_path(), disk_state)?;
135 self.tasks = tasks;
136 self.rounds_since_task = rounds_since_task;
137 let tasks = self.tasks.clone();
138 self.mutate_snapshot(|snapshot| {
139 snapshot.tasks = tasks;
140 });
141 Ok(())
142 }
143}