argentor-orchestrator 1.4.7

Multi-agent orchestration, task queues, and deployment for Argentor
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
use crate::types::{AgentProfile, AgentRole};
use argentor_agent::ModelConfig;
use argentor_security::{Capability, PermissionSet};

/// Create default agent profiles for the multi-agent system.
/// Uses the provided base config as template, adjusting per role.
pub fn default_profiles(base_config: &ModelConfig) -> Vec<AgentProfile> {
    vec![
        orchestrator_profile(base_config),
        spec_profile(base_config),
        coder_profile(base_config),
        tester_profile(base_config),
        reviewer_profile(base_config),
        architect_profile(base_config),
        security_auditor_profile(base_config),
        devops_profile(base_config),
        document_writer_profile(base_config),
    ]
}

/// Build a PermissionSet for the Spec role: memory_search only (no file/shell).
fn spec_permissions() -> PermissionSet {
    // Spec has no file or shell capabilities — only memory-based skills.
    PermissionSet::new()
}

/// Build a PermissionSet for the Coder role: FileRead + FileWrite + ShellExec (cargo/git) + memory.
fn coder_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::FileWrite {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::ShellExec {
        allowed_commands: vec!["cargo".to_string(), "git".to_string()],
    });
    perms
}

/// Build a PermissionSet for the Tester role: FileRead + ShellExec (cargo test only) + memory.
fn tester_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::ShellExec {
        allowed_commands: vec!["cargo".to_string()],
    });
    perms
}

/// Build a PermissionSet for the Reviewer role: FileRead only + memory.
fn reviewer_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms
}

/// Build a PermissionSet for the Architect role: FileRead + memory.
fn architect_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms
}

/// Build a PermissionSet for the SecurityAuditor role: FileRead + memory.
fn security_auditor_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms
}

/// Build a PermissionSet for the DevOps role: FileRead + FileWrite + ShellExec + NetworkAccess.
fn devops_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::FileWrite {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::ShellExec {
        allowed_commands: vec![
            "cargo".to_string(),
            "git".to_string(),
            "docker".to_string(),
            "kubectl".to_string(),
            "helm".to_string(),
        ],
    });
    perms.grant(Capability::NetworkAccess {
        allowed_hosts: vec!["*".to_string()],
    });
    perms
}

/// Build a PermissionSet for the DocumentWriter role: FileRead + FileWrite.
fn document_writer_permissions() -> PermissionSet {
    let mut perms = PermissionSet::new();
    perms.grant(Capability::FileRead {
        allowed_paths: vec![".".to_string()],
    });
    perms.grant(Capability::FileWrite {
        allowed_paths: vec![".".to_string()],
    });
    perms
}

fn orchestrator_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 30;
    model.temperature = 0.3;

    AgentProfile {
        role: AgentRole::Orchestrator,
        model,
        system_prompt: ORCHESTRATOR_PROMPT.to_string(),
        allowed_skills: vec![
            "agent_delegate".to_string(),
            "task_status".to_string(),
            "human_approval".to_string(),
            "artifact_store".to_string(),
            "memory_search".to_string(),
        ],
        tool_group: Some("orchestration".to_string()),
        max_turns: 30,
        permissions: PermissionSet::new(),
    }
}

fn spec_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 15;
    model.temperature = 0.4;

    AgentProfile {
        role: AgentRole::Spec,
        model,
        system_prompt: SPEC_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string(), "memory_store".to_string()],
        tool_group: Some("minimal".to_string()),
        max_turns: 15,
        permissions: spec_permissions(),
    }
}

fn coder_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 20;
    model.temperature = 0.2;

    AgentProfile {
        role: AgentRole::Coder,
        model,
        system_prompt: CODER_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 20,
        permissions: coder_permissions(),
    }
}

fn tester_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 15;
    model.temperature = 0.2;

    AgentProfile {
        role: AgentRole::Tester,
        model,
        system_prompt: TESTER_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 15,
        permissions: tester_permissions(),
    }
}

fn reviewer_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 10;
    model.temperature = 0.3;

    AgentProfile {
        role: AgentRole::Reviewer,
        model,
        system_prompt: REVIEWER_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string(), "human_approval".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 10,
        permissions: reviewer_permissions(),
    }
}

fn architect_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 20;
    model.temperature = 0.3;

    AgentProfile {
        role: AgentRole::Architect,
        model,
        system_prompt: ARCHITECT_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string(), "memory_store".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 20,
        permissions: architect_permissions(),
    }
}

fn security_auditor_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 15;
    model.temperature = 0.2;

    AgentProfile {
        role: AgentRole::SecurityAuditor,
        model,
        system_prompt: SECURITY_AUDITOR_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 15,
        permissions: security_auditor_permissions(),
    }
}

fn devops_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 20;
    model.temperature = 0.2;

    AgentProfile {
        role: AgentRole::DevOps,
        model,
        system_prompt: DEVOPS_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string(), "memory_store".to_string()],
        tool_group: Some("devops".to_string()),
        max_turns: 20,
        permissions: devops_permissions(),
    }
}

fn document_writer_profile(base: &ModelConfig) -> AgentProfile {
    let mut model = base.clone();
    model.max_turns = 15;
    model.temperature = 0.4;

    AgentProfile {
        role: AgentRole::DocumentWriter,
        model,
        system_prompt: DOCUMENT_WRITER_PROMPT.to_string(),
        allowed_skills: vec!["memory_search".to_string()],
        tool_group: Some("coding".to_string()),
        max_turns: 15,
        permissions: document_writer_permissions(),
    }
}

const ORCHESTRATOR_PROMPT: &str = "\
You are the Orchestrator agent in a multi-agent system called Argentor. \
Your job is to decompose complex tasks into subtasks, delegate them to \
specialized worker agents (Spec, Coder, Tester, Reviewer, Architect, \
SecurityAuditor, DevOps, DocumentWriter), and synthesize the final result.

Rules:
1. Break tasks into clear, independent subtasks when possible.
2. Assign each subtask to the most appropriate worker role.
3. Define dependencies between subtasks (e.g., Coder depends on Spec).
4. Monitor progress and handle failures by reassigning or adjusting.
5. Request human approval for high-risk operations (security changes, \
   deployments, data deletion).
6. Synthesize all artifacts into a coherent final deliverable.
7. Never write code yourself — delegate to Coder.
8. Use Architect for system design and API design tasks.
9. Use SecurityAuditor for security-focused reviews and audits.
10. Use DevOps for deployment, infrastructure, and CI/CD tasks.
11. Use DocumentWriter for documentation tasks.
";

const SPEC_PROMPT: &str = "\
You are the Spec agent in Argentor. Your job is to analyze requirements \
and produce clear, actionable technical specifications.

IMPORTANT: Respond with your specification as plain text. Do NOT call any tools — \
just write your analysis and specification directly in your response.

Rules:
1. Break requirements into concrete implementation tasks.
2. Define interfaces, data types, and contracts.
3. Identify edge cases, security considerations, and constraints.
4. Reference existing code patterns and utilities when applicable.
5. Output your specification directly as text.
";

const CODER_PROMPT: &str = "\
You are the Coder agent in Argentor. You write secure, idiomatic Rust code \
following the project's patterns and conventions.

IMPORTANT: Respond with your code as plain text. Do NOT call any tools — \
just write the code directly in your response. Use markdown code blocks.

Rules:
1. Follow the specification provided by the Spec agent.
2. Write memory-safe, secure code — no unwrap() in production paths.
3. Use existing project patterns (traits, error handling, async).
4. Keep code simple — no over-engineering or premature abstractions.
5. Include inline comments only where logic is non-obvious.
6. Output code directly in your response with file paths as comments.
";

const TESTER_PROMPT: &str = "\
You are the Tester agent in Argentor. You write comprehensive tests \
for Rust code.

IMPORTANT: Respond with your test code as plain text. Do NOT call any tools — \
just write the tests directly in your response. Use markdown code blocks.

Rules:
1. Write unit tests covering happy paths, edge cases, and error conditions.
2. Follow existing test patterns in the project (tokio::test for async).
3. Test security boundaries and permission checks.
4. Use descriptive test names (test_<function>_<scenario>).
5. Output test code directly in your response.
";

const REVIEWER_PROMPT: &str = "\
You are the Reviewer agent in Argentor. You review code for quality, \
security, and compliance.

IMPORTANT: Respond with your review as plain text. Do NOT call any tools — \
just write your review report directly in your response.

Rules:
1. Check for OWASP Top 10 vulnerabilities.
2. Verify capability-based permission checks are in place.
3. Ensure proper error handling (no unwrap in prod paths).
4. Check for compliance with GDPR, ISO 27001, ISO 42001 requirements.
5. Flag any issues that require human review.
6. Output your review report directly in your response.
";

const ARCHITECT_PROMPT: &str = "\
You are the Architect agent in Argentor. You design system architecture, \
define APIs, and produce technical design documents.

IMPORTANT: Respond with your design document as plain text. Do NOT call any tools — \
just write your architecture and design directly in your response.

Rules:
1. Design modular, scalable architectures following SOLID principles.
2. Define clear API contracts and data models.
3. Consider security boundaries, trust zones, and blast radius.
4. Document trade-offs and alternatives considered.
5. Ensure designs are compatible with the existing Argentor crate structure.
6. Output your design document directly in your response.
";

const SECURITY_AUDITOR_PROMPT: &str = "\
You are the SecurityAuditor agent in Argentor. You perform security reviews, \
vulnerability analysis, and compliance audits.

IMPORTANT: Respond with your security audit report as plain text. Do NOT call any tools — \
just write your findings directly in your response.

Rules:
1. Check for OWASP Top 10 vulnerabilities.
2. Audit capability-based permission boundaries.
3. Verify input validation, sanitization, and output encoding.
4. Check for secrets, credentials, or sensitive data exposure.
5. Assess cryptographic practices (TLS, key management).
6. Flag CRITICAL_SECURITY_ISSUE or NEEDS_HUMAN_REVIEW for serious findings.
7. Output your audit report directly in your response.
";

const DEVOPS_PROMPT: &str = "\
You are the DevOps agent in Argentor. You handle deployment, infrastructure, \
CI/CD pipelines, and operational tasks.

IMPORTANT: Respond with your infrastructure code or configuration as plain text. \
Do NOT call any tools — just write your output directly in your response.

Rules:
1. Write Dockerfiles, Helm charts, and CI/CD configurations.
2. Follow infrastructure-as-code best practices.
3. Ensure deployments are reproducible and rollback-safe.
4. Configure monitoring, logging, and alerting.
5. Apply security hardening (least privilege, network policies).
6. Output your configuration and scripts directly in your response.
";

const DOCUMENT_WRITER_PROMPT: &str = "\
You are the DocumentWriter agent in Argentor. You write and maintain \
technical documentation, guides, and API references.

IMPORTANT: Respond with your documentation as plain text. Do NOT call any tools — \
just write your documentation directly in your response.

Rules:
1. Write clear, concise documentation following the project style.
2. Include code examples where helpful.
3. Document public APIs with usage patterns and edge cases.
4. Keep READMEs, changelogs, and guides up to date.
5. Use proper Markdown formatting.
6. Output your documentation directly in your response.
";

#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
    use super::*;
    use argentor_agent::LlmProvider;

    fn test_config() -> ModelConfig {
        ModelConfig {
            provider: LlmProvider::Claude,
            model_id: "claude-sonnet-4-20250514".to_string(),
            api_key: "test-key".to_string(),
            api_base_url: None,
            temperature: 0.7,
            max_tokens: 4096,
            max_turns: 20,
            max_context_tokens: 200_000,
            fallback_models: Vec::new(),
            retry_policy: None,
        }
    }

    #[test]
    fn test_default_profiles_count() {
        let profiles = default_profiles(&test_config());
        assert_eq!(profiles.len(), 9);
    }

    #[test]
    fn test_all_roles_covered() {
        let profiles = default_profiles(&test_config());
        let roles: Vec<AgentRole> = profiles.iter().map(|p| p.role.clone()).collect();
        assert!(roles.contains(&AgentRole::Orchestrator));
        assert!(roles.contains(&AgentRole::Spec));
        assert!(roles.contains(&AgentRole::Coder));
        assert!(roles.contains(&AgentRole::Tester));
        assert!(roles.contains(&AgentRole::Reviewer));
        assert!(roles.contains(&AgentRole::Architect));
        assert!(roles.contains(&AgentRole::SecurityAuditor));
        assert!(roles.contains(&AgentRole::DevOps));
        assert!(roles.contains(&AgentRole::DocumentWriter));
    }

    #[test]
    fn test_orchestrator_has_delegate_skill() {
        let profiles = default_profiles(&test_config());
        let orch = profiles
            .iter()
            .find(|p| p.role == AgentRole::Orchestrator)
            .unwrap();
        assert!(orch.allowed_skills.contains(&"agent_delegate".to_string()));
        assert!(orch.allowed_skills.contains(&"human_approval".to_string()));
    }

    #[test]
    fn test_coder_low_temperature() {
        let profiles = default_profiles(&test_config());
        let coder = profiles
            .iter()
            .find(|p| p.role == AgentRole::Coder)
            .unwrap();
        assert!(coder.model.temperature <= 0.3);
    }

    #[test]
    fn test_profiles_have_system_prompts() {
        let profiles = default_profiles(&test_config());
        for profile in &profiles {
            assert!(!profile.system_prompt.is_empty());
        }
    }
}