oxios_kernel/token_maxing/planner.rs
1//! WorkPlanner — three-source task synthesis (RFC-031 §7).
2//!
3//! Selects the next task for the TokenMaxer from three prioritized sources,
4//! each filtered to non-destructive, bounded work (maxing runs unattended — no
5//! `rm`, no deploys, no outbound network beyond read):
6//!
7//! - **Source A — autonomous skills**: skills with frontmatter `autonomous:
8//! true`. This is the "자주 실행되던 스킬" axis.
9//! - **Source B — registered projects/mounts**: a bounded, read-mostly review
10//! task synthesized from each project's paths.
11//! - **Source C — recurring patterns**: stubbed (lowest priority; avoids
12//! inventing work).
13
14use std::collections::HashSet;
15use std::path::PathBuf;
16use std::sync::Arc;
17
18use super::session::TaskSource;
19
20/// A unit of work the TokenMaxer can dispatch on one provider.
21#[derive(Debug, Clone)]
22pub struct PlannedTask {
23 /// Which source produced this task.
24 pub source: TaskSource,
25 /// Human label (skill/project name) — for the status panel + report.
26 pub source_name: String,
27 /// The synthesized goal prompt handed to the agent.
28 pub goal: String,
29 /// Workspace paths to sandbox the agent to (project tasks only).
30 pub mount_paths: Vec<PathBuf>,
31}
32
33/// Selects the next task for a maxing run.
34pub struct WorkPlanner {
35 skills: Arc<crate::skill::SkillManager>,
36 projects: Option<Arc<crate::project::ProjectManager>>,
37}
38
39impl WorkPlanner {
40 pub fn new(
41 skills: Arc<crate::skill::SkillManager>,
42 projects: Option<Arc<crate::project::ProjectManager>>,
43 ) -> Self {
44 Self { skills, projects }
45 }
46
47 /// Pick the next task not already done this session (`done_goals`).
48 ///
49 /// Returns `None` when no eligible work remains — the maxer then terminates
50 /// the window early ("stopped: nothing to do") rather than fabricating work.
51 pub async fn next_task(&self, done_goals: &HashSet<String>) -> Option<PlannedTask> {
52 // Source A — autonomous-eligible skills.
53 for entry in self.skills.list_skills().await {
54 let autonomous = entry
55 .metadata
56 .as_ref()
57 .map(|m| m.autonomous)
58 .unwrap_or(false);
59 if !autonomous {
60 continue;
61 }
62 let goal = format!(
63 "[Skill: {}] {}. Perform this skill's routine work autonomously and \
64 summarize the outcome. Do not make destructive changes or run deploys.",
65 entry.skill.name, entry.skill.description
66 );
67 if done_goals.contains(&goal) {
68 continue;
69 }
70 return Some(PlannedTask {
71 source: TaskSource::Skill,
72 source_name: entry.skill.name.clone(),
73 goal,
74 mount_paths: Vec::new(),
75 });
76 }
77
78 // Source B — registered projects (read-mostly review).
79 if let Some(pm) = &self.projects {
80 for proj in pm.list_projects() {
81 if proj.paths.is_empty() {
82 // Non-code project — no filesystem to review.
83 continue;
84 }
85 let goal = format!(
86 "[Project: {}] Review the recent state of this project: summarize open \
87 work, recent changes, and any TODOs/FIXMEs. Read-only — do not modify \
88 files or run deploys.",
89 proj.name
90 );
91 if done_goals.contains(&goal) {
92 continue;
93 }
94 return Some(PlannedTask {
95 source: TaskSource::Project,
96 source_name: proj.name.clone(),
97 goal,
98 mount_paths: proj.paths.clone(),
99 });
100 }
101 }
102
103 // Source C — recurring patterns: intentionally stubbed (RFC-031 §7).
104 None
105 }
106}