Skip to content

Commit 8e5828f

Browse files
echobtfactorydroid
andauthored
feat(cortex-agents): implement Plan Mode & Spec Mode system (#207)
Implements AGENT_3 mission from ORCHESTRATE/AGENT_3_MODES.md. ## New Features ### Operation Mode System (cortex-agents) - Added OperationMode enum (Build/Plan/Spec) with full lifecycle support - Build mode: Full access to modify files and execute commands - Plan mode: Read-only, describes changes without applying them - Spec mode: Generates detailed implementation plans for user approval ### Spec Plan System (cortex-agents/src/spec/) - SpecPlan: Structured specification with title, summary, steps, file changes - SpecStep: Individual implementation step with dependencies - FileChange: Describes file operations (Create/Modify/Delete/Rename) - ApprovalManager: Handles async approval flow with timeout support - ModeTransition: Manages transitions between modes with system prompt updates ### Permission Filtering - filter_for_mode(): Restricts permissions based on operation mode - for_mode(): Factory method for mode-appropriate permissions - Plan/Spec modes block write operations ### TUI Integration (cortex-core, cortex-tui) - ModeIndicator widget: Displays current mode with icon and color - ToggleOperationMode action: Cycles Build -> Plan -> Spec -> Build - ApproveSpec/RejectSpec actions: Handle spec plan approval workflow - AppState.operation_mode: Tracks current mode in TUI state ## Tests - 53 tests added for cortex-agents spec system - 4 tests added for ModeIndicator widget - All existing tests pass Resolves: ORCHESTRATE/AGENT_3_MODES.md Co-authored-by: Droid Agent <droid@factory.ai>
1 parent de82de3 commit 8e5828f

File tree

11 files changed

+1580
-1
lines changed

11 files changed

+1580
-1
lines changed

cortex-agents/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@
77
//! - **general**: Sub-agent for parallel tasks
88
//! - **research**: Read-only investigation agent
99
//!
10+
//! # Operation Modes
11+
//!
12+
//! Agents can operate in different modes that control their capabilities:
13+
//! - **Build**: Full access to modify files and execute commands
14+
//! - **Plan**: Read-only, describes changes without applying them
15+
//! - **Spec**: Generates a detailed plan for user approval before building
16+
//!
17+
//! ```rust,ignore
18+
//! use cortex_agents::spec::OperationMode;
19+
//!
20+
//! let mode = OperationMode::Build;
21+
//! assert!(mode.can_write());
22+
//!
23+
//! let mode = OperationMode::Plan;
24+
//! assert!(!mode.can_write());
25+
//! ```
26+
//!
1027
//! # @Mention Support
1128
//!
1229
//! Users can invoke subagents directly using @agent syntax:
@@ -88,6 +105,7 @@ pub mod permission;
88105
pub mod prompt;
89106
pub mod registry;
90107
pub mod routing;
108+
pub mod spec;
91109
pub mod task;
92110

93111
pub use agent::{
@@ -115,6 +133,10 @@ pub use mention::{
115133
pub use permission::{Permission, PermissionConfig};
116134
pub use prompt::{build_system_prompt, build_system_prompt_with_mentions, build_tool_context};
117135
pub use registry::{AgentRegistry, SMALL_MODEL_AGENTS};
136+
pub use spec::{
137+
ApprovalDecision, ApprovalManager, ApprovalRequest, ChangeType, FileChange, ModeTransition,
138+
OperationMode, SpecPlan, SpecStep,
139+
};
118140

119141
// Re-export collaboration types
120142
pub use collab::{

cortex-agents/src/permission.rs

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
//! Permission system for agents.
2+
//!
3+
//! This module provides permission configuration for controlling what agents
4+
//! can do, including file operations, command execution, and web access.
25
6+
use crate::spec::OperationMode;
37
use serde::{Deserialize, Serialize};
48
use std::collections::HashMap;
59

@@ -84,6 +88,53 @@ impl PermissionConfig {
8488
pub fn can_execute_bash(&self, command: &str) -> bool {
8589
self.bash.check(command).is_allowed()
8690
}
91+
92+
/// Filter permissions based on the operation mode.
93+
///
94+
/// In Plan and Spec modes, write operations are denied.
95+
/// This returns a new `PermissionConfig` with appropriate restrictions.
96+
///
97+
/// # Example
98+
///
99+
/// ```rust
100+
/// use cortex_agents::permission::PermissionConfig;
101+
/// use cortex_agents::spec::OperationMode;
102+
///
103+
/// let config = PermissionConfig::full_access();
104+
/// let filtered = config.filter_for_mode(OperationMode::Plan);
105+
/// assert!(filtered.edit.is_denied());
106+
/// ```
107+
pub fn filter_for_mode(&self, mode: OperationMode) -> Self {
108+
match mode {
109+
OperationMode::Build => self.clone(),
110+
OperationMode::Plan | OperationMode::Spec => {
111+
Self {
112+
edit: Permission::Deny,
113+
bash: BashPermissions::read_only(),
114+
// Keep read-only permissions
115+
webfetch: self.webfetch,
116+
doom_loop: self.doom_loop,
117+
external_directory: self.external_directory,
118+
}
119+
}
120+
}
121+
}
122+
123+
/// Check if write operations are allowed in the current config.
124+
pub fn can_write(&self) -> bool {
125+
self.edit.is_allowed()
126+
}
127+
128+
/// Create a permission config appropriate for the given mode.
129+
///
130+
/// This is a convenience method that returns the right config
131+
/// for each operation mode.
132+
pub fn for_mode(mode: OperationMode) -> Self {
133+
match mode {
134+
OperationMode::Build => Self::full_access(),
135+
OperationMode::Plan | OperationMode::Spec => Self::read_only(),
136+
}
137+
}
87138
}
88139

89140
/// Bash command permissions with pattern matching.
@@ -230,4 +281,48 @@ mod tests {
230281
assert!(config.edit.is_denied());
231282
assert!(config.webfetch.is_allowed());
232283
}
284+
285+
#[test]
286+
fn test_filter_for_mode_build() {
287+
let config = PermissionConfig::full_access();
288+
let filtered = config.filter_for_mode(OperationMode::Build);
289+
290+
assert!(filtered.edit.is_allowed());
291+
assert!(filtered.can_write());
292+
}
293+
294+
#[test]
295+
fn test_filter_for_mode_plan() {
296+
let config = PermissionConfig::full_access();
297+
let filtered = config.filter_for_mode(OperationMode::Plan);
298+
299+
assert!(filtered.edit.is_denied());
300+
assert!(!filtered.can_write());
301+
// Read-only bash commands should still work
302+
assert!(filtered.bash.check("ls -la").is_allowed());
303+
assert!(filtered.bash.check("cat file.txt").is_allowed());
304+
// Write commands should be denied/ask
305+
assert!(filtered.bash.check("rm -rf /").is_denied());
306+
}
307+
308+
#[test]
309+
fn test_filter_for_mode_spec() {
310+
let config = PermissionConfig::full_access();
311+
let filtered = config.filter_for_mode(OperationMode::Spec);
312+
313+
assert!(filtered.edit.is_denied());
314+
assert!(!filtered.can_write());
315+
}
316+
317+
#[test]
318+
fn test_for_mode() {
319+
let build_config = PermissionConfig::for_mode(OperationMode::Build);
320+
assert!(build_config.can_write());
321+
322+
let plan_config = PermissionConfig::for_mode(OperationMode::Plan);
323+
assert!(!plan_config.can_write());
324+
325+
let spec_config = PermissionConfig::for_mode(OperationMode::Spec);
326+
assert!(!spec_config.can_write());
327+
}
233328
}

0 commit comments

Comments
 (0)