1use super::{
4 guard::GuardCapability,
5 mission_lease::{WorkspaceLease, leases_expr},
6};
7use crate::{AgentPattern, AgentPatternSlot};
8use sim_kernel::{Expr, Symbol};
9
10#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct AgentMission {
13 id: Symbol,
14 goal: String,
15 scope: MissionScope,
16 allowed_repos: Vec<String>,
17 denied_paths: Vec<String>,
18 code_free_repos: Vec<String>,
19 capabilities: Vec<GuardCapability>,
20 leases: Vec<WorkspaceLease>,
21 validations: Vec<MissionRun>,
22 docs_runs: Vec<MissionRun>,
23 decision_points: Vec<HumanDecisionPoint>,
24 recipe_pattern: Symbol,
25 roles: Vec<AtelierAgentRole>,
26 evidence_stream: Symbol,
27}
28
29impl AgentMission {
30 pub fn new(id: Symbol, leased_repo: impl Into<String>) -> Self {
32 let leased_repo = leased_repo.into();
33 Self {
34 id: id.clone(),
35 goal: "guarded Atelier mission".to_owned(),
36 scope: MissionScope::repo(leased_repo.clone()),
37 allowed_repos: vec![leased_repo.clone()],
38 denied_paths: vec![".meta-workspace/".to_owned()],
39 code_free_repos: Vec::new(),
40 capabilities: Vec::new(),
41 leases: Vec::new(),
42 validations: Vec::new(),
43 docs_runs: Vec::new(),
44 decision_points: Vec::new(),
45 recipe_pattern: Symbol::new("a30-013-code-generation"),
46 roles: default_roles(),
47 evidence_stream: Symbol::qualified("atelier/dev", id.name.as_ref()),
48 }
49 }
50
51 pub fn with_capability(mut self, capability: GuardCapability) -> Self {
53 push_unique(&mut self.capabilities, capability);
54 self
55 }
56
57 pub fn with_goal(mut self, goal: impl Into<String>) -> Self {
59 self.goal = goal.into();
60 self
61 }
62
63 pub fn with_scope_summary(mut self, summary: impl Into<String>) -> Self {
65 self.scope.summary = summary.into();
66 self
67 }
68
69 pub fn with_allowed_repo(mut self, repo: impl Into<String>) -> Self {
71 push_unique(&mut self.allowed_repos, repo.into());
72 self
73 }
74
75 pub fn with_denied_path(mut self, path: impl Into<String>) -> Self {
77 push_unique(&mut self.denied_paths, path.into());
78 self
79 }
80
81 pub fn with_code_free_repo(mut self, repo: impl Into<String>) -> Self {
83 push_unique(&mut self.code_free_repos, repo.into());
84 self
85 }
86
87 pub fn with_lease(mut self, lease: WorkspaceLease) -> Self {
89 self.leases.push(lease);
90 self
91 }
92
93 pub fn with_validation(mut self, run: MissionRun) -> Self {
95 self.validations.push(run);
96 self
97 }
98
99 pub fn with_docs_run(mut self, run: MissionRun) -> Self {
101 self.docs_runs.push(run);
102 self
103 }
104
105 pub fn with_decision_point(mut self, point: HumanDecisionPoint) -> Self {
107 self.decision_points.push(point);
108 self
109 }
110
111 pub fn with_recipe_pattern(mut self, recipe_pattern: Symbol) -> Self {
113 self.recipe_pattern = recipe_pattern;
114 self
115 }
116
117 pub fn with_evidence_stream(mut self, evidence_stream: Symbol) -> Self {
119 self.evidence_stream = evidence_stream;
120 self
121 }
122
123 pub fn id(&self) -> &Symbol {
125 &self.id
126 }
127
128 pub fn goal(&self) -> &str {
130 &self.goal
131 }
132
133 pub fn scope(&self) -> &MissionScope {
135 &self.scope
136 }
137
138 pub fn leased_repo(&self) -> &str {
140 &self.scope.primary_repo
141 }
142
143 pub fn allowed_repos(&self) -> &[String] {
145 &self.allowed_repos
146 }
147
148 pub fn denied_paths(&self) -> &[String] {
150 &self.denied_paths
151 }
152
153 pub fn code_free_repos(&self) -> &[String] {
155 &self.code_free_repos
156 }
157
158 pub fn capabilities(&self) -> &[GuardCapability] {
160 &self.capabilities
161 }
162
163 pub fn leases(&self) -> &[WorkspaceLease] {
165 &self.leases
166 }
167
168 pub fn validations(&self) -> &[MissionRun] {
170 &self.validations
171 }
172
173 pub fn docs_runs(&self) -> &[MissionRun] {
175 &self.docs_runs
176 }
177
178 pub fn decision_points(&self) -> &[HumanDecisionPoint] {
180 &self.decision_points
181 }
182
183 pub fn recipe_pattern(&self) -> &Symbol {
185 &self.recipe_pattern
186 }
187
188 pub fn roles(&self) -> &[AtelierAgentRole] {
190 &self.roles
191 }
192
193 pub fn evidence_stream(&self) -> &Symbol {
195 &self.evidence_stream
196 }
197
198 pub fn descriptor(&self) -> AgentPattern {
200 AgentPattern {
201 id: self.id.clone(),
202 description: Some(format!(
203 "Atelier mission using recipe pattern {}",
204 self.recipe_pattern
205 )),
206 sense: slots([
207 ("goal", self.goal.as_str()),
208 ("scope", self.scope.summary.as_str()),
209 ]),
210 model: slots([("fake-cassette-runner", "deterministic runner surface")]),
211 plan: slots([("f3-decompose", "split the mission into leased sub-tasks")]),
212 act: role_slots(&self.roles),
213 memory: slots([("f2-radar-retrieve", "rank query over the Atelier index")]),
214 tools: slots(
215 self.validations
216 .iter()
217 .chain(self.docs_runs.iter())
218 .map(|run| {
219 (
220 run.label.as_str(),
221 "required mission command recorded in evidence",
222 )
223 }),
224 ),
225 policy: slots([
226 ("allowed-repos", "repository allow-list"),
227 ("denied-paths", "path deny-list"),
228 ("guard-capabilities", "granted Guideline Firewall tokens"),
229 ]),
230 evaluation: slots([
231 (
232 "f3-reflect",
233 "reflect over edits against retrieved evidence",
234 ),
235 ("f4-confidence", "statistics-scored confidence"),
236 ]),
237 guardrail: slots([("guideline-firewall", "capability-gated action checks")]),
238 trace: slots([
239 (
240 "dev-cassette-ledger",
241 "read, edit, guard, validation, and refusal evidence",
242 ),
243 (
244 "f6-attribution",
245 "attribution card from the evidence ledger",
246 ),
247 ]),
248 extra: vec![
249 (
250 Symbol::new("recipe-pattern"),
251 Expr::Symbol(self.recipe_pattern.clone()),
252 ),
253 (
254 Symbol::new("allowed-repos"),
255 string_list_expr(&self.allowed_repos),
256 ),
257 (Symbol::new("leases"), leases_expr(&self.leases)),
258 ],
259 }
260 }
261
262 pub fn as_expr(&self) -> Expr {
264 Expr::Map(vec![
265 key("kind", Expr::Symbol(Symbol::new("agent-mission"))),
266 key("id", Expr::Symbol(self.id.clone())),
267 key("goal", Expr::String(self.goal.clone())),
268 key("scope", self.scope.as_expr()),
269 key("allowed-repos", string_list_expr(&self.allowed_repos)),
270 key("denied-paths", string_list_expr(&self.denied_paths)),
271 key("code-free-repos", string_list_expr(&self.code_free_repos)),
272 key("leases", leases_expr(&self.leases)),
273 key("validations", runs_expr(&self.validations)),
274 key("docs-runs", runs_expr(&self.docs_runs)),
275 key("decision-points", decisions_expr(&self.decision_points)),
276 key("recipe-pattern", Expr::Symbol(self.recipe_pattern.clone())),
277 key("descriptor", self.descriptor().as_expr()),
278 ])
279 }
280}
281
282#[derive(Clone, Debug, PartialEq, Eq)]
284pub struct MissionScope {
285 pub primary_repo: String,
287 pub summary: String,
289}
290
291impl MissionScope {
292 pub fn repo(repo: impl Into<String>) -> Self {
294 let repo = repo.into();
295 Self {
296 primary_repo: repo.clone(),
297 summary: format!("changes in {repo}"),
298 }
299 }
300
301 fn as_expr(&self) -> Expr {
302 Expr::Map(vec![
303 key("primary-repo", Expr::String(self.primary_repo.clone())),
304 key("summary", Expr::String(self.summary.clone())),
305 ])
306 }
307}
308
309#[derive(Clone, Debug, PartialEq, Eq)]
311pub struct MissionRun {
312 pub label: String,
314 pub command: String,
316}
317
318impl MissionRun {
319 pub fn new(label: impl Into<String>, command: impl Into<String>) -> Self {
321 Self {
322 label: label.into(),
323 command: command.into(),
324 }
325 }
326
327 pub(crate) fn as_expr(&self) -> Expr {
328 Expr::Map(vec![
329 key("label", Expr::String(self.label.clone())),
330 key("command", Expr::String(self.command.clone())),
331 ])
332 }
333}
334
335#[derive(Clone, Debug, PartialEq, Eq)]
337pub struct HumanDecisionPoint {
338 pub id: String,
340 pub question: String,
342}
343
344impl HumanDecisionPoint {
345 pub fn new(id: impl Into<String>, question: impl Into<String>) -> Self {
347 Self {
348 id: id.into(),
349 question: question.into(),
350 }
351 }
352
353 pub(crate) fn as_expr(&self) -> Expr {
354 Expr::Map(vec![
355 key("id", Expr::String(self.id.clone())),
356 key("question", Expr::String(self.question.clone())),
357 ])
358 }
359}
360
361#[derive(Clone, Debug, PartialEq, Eq)]
363pub enum AtelierAgentRole {
364 Cartographer,
366 Editor,
368 CodecSpecialist,
370 Guard,
372 Validator,
374 DocsAgent,
376 PinAgent,
378 Reviewer,
380 HumanGate,
382}
383
384impl AtelierAgentRole {
385 pub fn as_symbol(&self) -> Symbol {
387 Symbol::qualified("atelier/agent", self.label())
388 }
389
390 pub fn label(&self) -> &'static str {
392 match self {
393 Self::Cartographer => "cartographer",
394 Self::Editor => "editor",
395 Self::CodecSpecialist => "codec-specialist",
396 Self::Guard => "guard",
397 Self::Validator => "validator",
398 Self::DocsAgent => "docs-agent",
399 Self::PinAgent => "pin-agent",
400 Self::Reviewer => "reviewer",
401 Self::HumanGate => "human-gate",
402 }
403 }
404}
405
406fn default_roles() -> Vec<AtelierAgentRole> {
407 vec![
408 AtelierAgentRole::Cartographer,
409 AtelierAgentRole::Editor,
410 AtelierAgentRole::CodecSpecialist,
411 AtelierAgentRole::Guard,
412 AtelierAgentRole::Validator,
413 AtelierAgentRole::DocsAgent,
414 AtelierAgentRole::PinAgent,
415 AtelierAgentRole::Reviewer,
416 AtelierAgentRole::HumanGate,
417 ]
418}
419
420fn role_slots(roles: &[AtelierAgentRole]) -> Vec<AgentPatternSlot> {
421 roles
422 .iter()
423 .map(|role| AgentPatternSlot {
424 name: role.as_symbol(),
425 description: Some("Atelier mission handoff role".to_owned()),
426 extra: Vec::new(),
427 })
428 .collect()
429}
430
431fn slots(
432 slots: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
433) -> Vec<AgentPatternSlot> {
434 slots
435 .into_iter()
436 .map(|(name, description)| AgentPatternSlot {
437 name: Symbol::new(name.into()),
438 description: Some(description.into()),
439 extra: Vec::new(),
440 })
441 .collect()
442}
443
444fn runs_expr(runs: &[MissionRun]) -> Expr {
445 Expr::List(runs.iter().map(MissionRun::as_expr).collect())
446}
447
448fn decisions_expr(decisions: &[HumanDecisionPoint]) -> Expr {
449 Expr::List(decisions.iter().map(HumanDecisionPoint::as_expr).collect())
450}
451
452fn string_list_expr(values: &[String]) -> Expr {
453 Expr::List(values.iter().cloned().map(Expr::String).collect())
454}
455
456pub(crate) fn key(name: &str, value: Expr) -> (Expr, Expr) {
457 (Expr::Symbol(Symbol::new(name)), value)
458}
459
460fn push_unique<T: PartialEq>(values: &mut Vec<T>, value: T) {
461 if !values.contains(&value) {
462 values.push(value);
463 }
464}