Skip to content

Commit c127fad

Browse files
author
Symbiont OSS Sync
committed
scheduler and context management improvements
1 parent 46808d8 commit c127fad

File tree

23 files changed

+3978
-292
lines changed

23 files changed

+3978
-292
lines changed

Cargo.lock

Lines changed: 450 additions & 70 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,16 @@ symbi/
102102
### ✅ Community Features (OSS)
103103
- **DSL Grammar**: Complete Tree-sitter grammar for agent definitions
104104
- **Agent Runtime**: Task scheduling, resource management, lifecycle control
105+
- **Real Task Execution**: Actual process spawning with comprehensive monitoring and metrics
106+
- **Graceful Shutdown**: Coordinated shutdown with resource cleanup and timeout handling
105107
- **Tier 1 Sandboxing**: Docker containerized isolation for agent operations
106108
- **MCP Integration**: Model Context Protocol client for external tools
107-
- **SchemaPin Security**: Basic cryptographic tool verification
109+
- **SchemaPin Security**: Basic cryptographic tool verification
108110
- **RAG Engine**: Retrieval-augmented generation with vector search
109-
- **Context Management**: Persistent agent memory and knowledge storage
111+
- **Advanced Context Management**: Sophisticated memory with importance calculation and search modes
112+
- **Multi-Modal Search**: Keyword, temporal, similarity, and hybrid search capabilities
113+
- **Access Control Integration**: Policy engine connected context management with agent-scoped access
114+
- **Context Archiving**: Automatic archiving with retention policies and compressed storage
110115
- **Vector Database**: Qdrant integration for semantic search
111116
- **Comprehensive Secrets Management**: HashiCorp Vault integration with multiple auth methods
112117
- **Encrypted File Backend**: AES-256-GCM encryption with OS keychain integration

crates/runtime/Cargo.toml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ path = "examples/full_system.rs"
2222
name = "symbi-mcp"
2323
path = "src/bin/symbiont_mcp.rs"
2424
[dependencies]
25+
symbi-dsl = { path = "../dsl" }
2526
tokio = { version = "1.0", features = ["full"] }
2627
serde = { version = "1.0", features = ["derive"] }
2728
serde_json = "1.0"
@@ -45,9 +46,9 @@ bytes = { version = "1.0", features = ["serde"] }
4546
tempfile = "3.0"
4647
qdrant-client = "1.14.0"
4748
flate2 = "1.0"
48-
candle-core = { version = "0.3", optional = true }
49-
candle-nn = { version = "0.3", optional = true }
50-
candle-transformers = { version = "0.3", optional = true }
49+
candle-core = { version = "0.9.1", optional = true }
50+
candle-nn = { version = "0.9.1", optional = true }
51+
candle-transformers = { version = "0.9.1", optional = true }
5152
tokenizers = { version = "0.15", optional = true }
5253
hf-hub = { version = "0.3", optional = true }
5354
regex = "1.0"
@@ -64,6 +65,7 @@ sha2 = "0.10"
6465
hex = "0.4"
6566
argon2 = "0.5"
6667
vaultrs = "0.7"
68+
sysinfo = "0.30"
6769
# OS keychain access dependencies
6870
keyring = { version = "2.0", optional = true }
6971
security-framework = { version = "2.9", optional = true }
@@ -75,6 +77,7 @@ axum = { version = "0.7", optional = true }
7577
tower = { version = "0.4", optional = true }
7678
tower-http = { version = "0.5", features = ["cors", "trace"], optional = true }
7779
tokio-tungstenite = { version = "0.21", optional = true }
80+
governor = { version = "0.10", optional = true }
7881

7982
# Enterprise features removed for OSS build
8083

@@ -87,7 +90,7 @@ criterion = "0.5"
8790
default = ["vector-db", "keychain"]
8891
vector-db = []
8992
embedding-models = ["candle-core", "candle-nn", "candle-transformers", "tokenizers", "hf-hub"]
90-
http-api = ["axum", "tower", "tower-http", "tokio-tungstenite"]
93+
http-api = ["axum", "tower", "tower-http", "tokio-tungstenite", "governor"]
9194
http-input = ["axum", "tower", "tower-http"]
9295
keychain = ["keyring", "security-framework", "secret-service", "winapi"]
9396
enterprise = [] # Enterprise feature for conditional compilation

crates/runtime/examples/context_example.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
3737
..Default::default()
3838
};
3939

40-
let context_manager = Arc::new(StandardContextManager::new(config));
40+
let context_manager = Arc::new(StandardContextManager::new(config, "system").await?);
4141
context_manager.initialize().await?;
4242
println!("✓ Context manager initialized");
4343

crates/runtime/examples/rag_example.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
2323
println!("\n=== Initializing RAG Engine ===");
2424

2525
let context_manager_config = ContextManagerConfig::default();
26-
let context_manager = Arc::new(StandardContextManager::new(context_manager_config));
26+
let context_manager = Arc::new(StandardContextManager::new(context_manager_config, "system").await?);
2727
let rag_engine = StandardRAGEngine::new(context_manager);
2828

2929
// Configure the RAG engine

crates/runtime/src/api/middleware.rs

Lines changed: 185 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@
66
#[cfg(feature = "http-api")]
77
use axum::{extract::Request, http::StatusCode, middleware::Next, response::Response};
88

9+
#[cfg(feature = "http-api")]
10+
use governor::{
11+
clock::DefaultClock,
12+
state::{InMemoryState, NotKeyed},
13+
Quota, RateLimiter,
14+
};
15+
16+
#[cfg(feature = "http-api")]
17+
use std::{net::IpAddr, num::NonZeroU32, sync::{Arc, OnceLock}};
18+
19+
#[cfg(feature = "http-api")]
20+
use dashmap::DashMap;
21+
922
/// Authentication middleware for bearer token validation
1023
#[cfg(feature = "http-api")]
1124
pub async fn auth_middleware(request: Request, next: Next) -> Result<Response, StatusCode> {
@@ -40,38 +53,193 @@ pub async fn auth_middleware(request: Request, next: Next) -> Result<Response, S
4053
Ok(next.run(request).await)
4154
}
4255

43-
/// Rate limiting middleware (placeholder)
56+
/// Global rate limiter store for per-IP rate limiting
57+
#[cfg(feature = "http-api")]
58+
static RATE_LIMITERS: OnceLock<DashMap<IpAddr, Arc<RateLimiter<NotKeyed, InMemoryState, DefaultClock>>>> = OnceLock::new();
59+
60+
/// Get or create a rate limiter for a specific IP address
61+
#[cfg(feature = "http-api")]
62+
fn get_rate_limiter_for_ip(ip: IpAddr) -> Arc<RateLimiter<NotKeyed, InMemoryState, DefaultClock>> {
63+
let limiters = RATE_LIMITERS.get_or_init(DashMap::new);
64+
65+
// Check if limiter exists, if not create one
66+
if let Some(limiter) = limiters.get(&ip) {
67+
Arc::clone(&limiter)
68+
} else {
69+
// Create a rate limiter: 100 requests per minute (roughly 1.67 requests per second)
70+
let quota = Quota::per_minute(NonZeroU32::new(100).unwrap());
71+
let limiter = Arc::new(RateLimiter::direct(quota));
72+
limiters.insert(ip, Arc::clone(&limiter));
73+
limiter
74+
}
75+
}
76+
77+
/// Extract client IP address from request
78+
#[cfg(feature = "http-api")]
79+
fn extract_client_ip(request: &Request) -> IpAddr {
80+
// Try to get real IP from X-Forwarded-For header first (for proxy setups)
81+
if let Some(forwarded_for) = request.headers().get("x-forwarded-for") {
82+
if let Ok(forwarded_str) = forwarded_for.to_str() {
83+
// X-Forwarded-For can contain multiple IPs, take the first one
84+
if let Some(first_ip) = forwarded_str.split(',').next() {
85+
if let Ok(ip) = first_ip.trim().parse::<IpAddr>() {
86+
return ip;
87+
}
88+
}
89+
}
90+
}
91+
92+
// Try X-Real-IP header
93+
if let Some(real_ip) = request.headers().get("x-real-ip") {
94+
if let Ok(real_ip_str) = real_ip.to_str() {
95+
if let Ok(ip) = real_ip_str.parse::<IpAddr>() {
96+
return ip;
97+
}
98+
}
99+
}
100+
101+
// Fallback to connection info or default
102+
// In a real setup, you'd extract this from the connection info
103+
// For now, we'll use a default IP as fallback
104+
"127.0.0.1".parse().unwrap()
105+
}
106+
107+
/// Rate limiting middleware using token bucket algorithm
108+
///
109+
/// This middleware implements per-IP rate limiting with a token bucket algorithm.
110+
/// Each IP address gets 100 requests per minute (approximately 1.67 RPS).
111+
///
112+
/// Rate limiters are stored in a global concurrent HashMap and are created
113+
/// on-demand for each unique IP address.
44114
#[cfg(feature = "http-api")]
45115
pub async fn rate_limit_middleware(request: Request, next: Next) -> Result<Response, StatusCode> {
46-
// TODO: Implement rate limiting logic
47-
// For now, just pass through all requests
48-
Ok(next.run(request).await)
116+
// Extract client IP address
117+
let client_ip = extract_client_ip(&request);
118+
119+
// Get the rate limiter for this IP
120+
let rate_limiter = get_rate_limiter_for_ip(client_ip);
121+
122+
// Check if the request is allowed
123+
match rate_limiter.check() {
124+
Ok(_) => {
125+
// Request is allowed, proceed
126+
Ok(next.run(request).await)
127+
}
128+
Err(_) => {
129+
// Rate limit exceeded
130+
tracing::warn!("Rate limit exceeded for IP: {}", client_ip);
131+
Err(StatusCode::TOO_MANY_REQUESTS)
132+
}
133+
}
49134
}
50135

51-
/// Request logging middleware (placeholder)
136+
/// Enhanced request logging middleware with structured logging
137+
///
138+
/// Logs comprehensive request details including:
139+
/// - HTTP method and URI
140+
/// - Response status code and processing latency
141+
/// - Client IP address and response body size
142+
/// - Uses structured logging with tracing spans for request grouping
52143
#[cfg(feature = "http-api")]
53144
pub async fn logging_middleware(request: Request, next: Next) -> Result<Response, StatusCode> {
54-
// TODO: Implement request logging
55-
// For now, just pass through all requests
145+
use std::time::Instant;
146+
147+
// Extract request details
56148
let method = request.method().clone();
57149
let uri = request.uri().clone();
58-
59-
tracing::debug!("Incoming request: {} {}", method, uri);
60-
150+
let client_ip = extract_client_ip(&request);
151+
152+
// Create a structured span for this request
153+
let span = tracing::info_span!(
154+
"http_request",
155+
method = %method,
156+
uri = %uri,
157+
client_ip = %client_ip,
158+
status_code = tracing::field::Empty,
159+
latency_ms = tracing::field::Empty,
160+
response_size = tracing::field::Empty,
161+
);
162+
163+
let _guard = span.enter();
164+
165+
// Record start time for latency calculation
166+
let start_time = Instant::now();
167+
168+
tracing::info!("Processing request");
169+
170+
// Process the request
61171
let response = next.run(request).await;
62-
63-
tracing::debug!("Response status: {}", response.status());
64-
172+
173+
// Calculate latency
174+
let latency = start_time.elapsed();
175+
let latency_ms = latency.as_millis() as u64;
176+
177+
// Extract response details
178+
let status_code = response.status();
179+
180+
// Try to extract response body size from Content-Length header
181+
let response_size = response
182+
.headers()
183+
.get("content-length")
184+
.and_then(|h| h.to_str().ok())
185+
.and_then(|s| s.parse::<u64>().ok())
186+
.unwrap_or(0);
187+
188+
// Record additional fields in the span
189+
span.record("status_code", status_code.as_u16());
190+
span.record("latency_ms", latency_ms);
191+
span.record("response_size", response_size);
192+
193+
// Log completion with all details
194+
tracing::info!(
195+
status_code = status_code.as_u16(),
196+
latency_ms = latency_ms,
197+
response_size = response_size,
198+
"Request completed"
199+
);
200+
65201
Ok(response)
66202
}
67203

68-
/// Security headers middleware (placeholder)
204+
/// Security headers middleware
205+
///
206+
/// Adds essential security headers to all HTTP responses:
207+
/// - Strict-Transport-Security: Enforces HTTPS connections
208+
/// - X-Content-Type-Options: Prevents MIME type sniffing
209+
/// - X-Frame-Options: Prevents clickjacking attacks
210+
/// - Content-Security-Policy: Restricts resource loading
69211
#[cfg(feature = "http-api")]
70212
pub async fn security_headers_middleware(
71213
request: Request,
72214
next: Next,
73215
) -> Result<Response, StatusCode> {
74-
// TODO: Add security headers
75-
// For now, just pass through all requests
76-
Ok(next.run(request).await)
216+
use axum::http::HeaderValue;
217+
218+
// Process the request
219+
let mut response = next.run(request).await;
220+
221+
// Add security headers to the response
222+
let headers = response.headers_mut();
223+
224+
headers.insert(
225+
"strict-transport-security",
226+
HeaderValue::from_static("max-age=63072000; includeSubDomains; preload")
227+
);
228+
229+
headers.insert(
230+
"x-content-type-options",
231+
HeaderValue::from_static("nosniff")
232+
);
233+
234+
headers.insert(
235+
"x-frame-options",
236+
HeaderValue::from_static("DENY")
237+
);
238+
239+
headers.insert(
240+
"content-security-policy",
241+
HeaderValue::from_static("default-src 'self'; frame-ancestors 'none'")
242+
);
243+
244+
Ok(response)
77245
}

crates/runtime/src/api/routes.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,9 @@ pub async fn execute_workflow(
4444
#[cfg(feature = "http-api")]
4545
pub async fn get_agent_status(
4646
State(provider): State<Arc<dyn RuntimeApiProvider>>,
47-
Path(_agent_id): Path<String>,
47+
Path(agent_id): Path<AgentId>,
4848
) -> Result<Json<AgentStatusResponse>, (StatusCode, Json<ErrorResponse>)> {
49-
let _agent_id = AgentId::new(); // TODO: Parse agent_id from string parameter
50-
51-
match provider.get_agent_status(_agent_id).await {
49+
match provider.get_agent_status(agent_id).await {
5250
Ok(status) => Ok(Json(status)),
5351
Err(e) => Err((
5452
StatusCode::NOT_FOUND,
@@ -123,7 +121,7 @@ pub async fn create_agent(
123121
#[cfg(feature = "http-api")]
124122
pub async fn update_agent(
125123
State(provider): State<Arc<dyn RuntimeApiProvider>>,
126-
Path(agent_id): Path<String>,
124+
Path(agent_id): Path<AgentId>,
127125
Json(request): Json<UpdateAgentRequest>,
128126
) -> Result<Json<UpdateAgentResponse>, (StatusCode, Json<ErrorResponse>)> {
129127
match provider.update_agent(agent_id, request).await {
@@ -143,7 +141,7 @@ pub async fn update_agent(
143141
#[cfg(feature = "http-api")]
144142
pub async fn delete_agent(
145143
State(provider): State<Arc<dyn RuntimeApiProvider>>,
146-
Path(agent_id): Path<String>,
144+
Path(agent_id): Path<AgentId>,
147145
) -> Result<Json<DeleteAgentResponse>, (StatusCode, Json<ErrorResponse>)> {
148146
match provider.delete_agent(agent_id).await {
149147
Ok(response) => Ok(Json(response)),
@@ -162,7 +160,7 @@ pub async fn delete_agent(
162160
#[cfg(feature = "http-api")]
163161
pub async fn execute_agent(
164162
State(provider): State<Arc<dyn RuntimeApiProvider>>,
165-
Path(agent_id): Path<String>,
163+
Path(agent_id): Path<AgentId>,
166164
Json(request): Json<ExecuteAgentRequest>,
167165
) -> Result<Json<ExecuteAgentResponse>, (StatusCode, Json<ErrorResponse>)> {
168166
match provider.execute_agent(agent_id, request).await {
@@ -182,7 +180,7 @@ pub async fn execute_agent(
182180
#[cfg(feature = "http-api")]
183181
pub async fn get_agent_history(
184182
State(provider): State<Arc<dyn RuntimeApiProvider>>,
185-
Path(agent_id): Path<String>,
183+
Path(agent_id): Path<AgentId>,
186184
) -> Result<Json<GetAgentHistoryResponse>, (StatusCode, Json<ErrorResponse>)> {
187185
match provider.get_agent_history(agent_id).await {
188186
Ok(response) => Ok(Json(response)),

0 commit comments

Comments
 (0)