# systemprompt-traits
Minimal shared traits and contracts for systemprompt.io.
[](https://crates.io/crates/systemprompt-traits)
[](https://docs.rs/systemprompt-traits)
[](LICENSE)
## Overview
**Part of the Shared layer in the systemprompt.io architecture.**
This crate provides the core trait definitions that enable polymorphism, dependency injection, and consistent patterns across the systemprompt.io codebase.
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
systemprompt-traits = "0.0.1"
```
## Feature Flags
| `web` | No | Axum router types for `ApiModule` trait |
## Traits
### Repository Traits
**`Repository`** - Base trait for all repository implementations
```rust
use systemprompt_traits::{Repository, RepositoryError};
impl Repository for MyRepository {
type Pool = DbPool;
type Error = RepositoryError;
fn pool(&self) -> &Self::Pool {
&self.db_pool
}
}
```
**`CrudRepository<T>`** - Generic CRUD operations trait
```rust
use systemprompt_traits::CrudRepository;
impl CrudRepository<User> for UserRepository {
type Id = String;
async fn create(&self, entity: User) -> Result<User, Self::Error> { ... }
async fn get(&self, id: Self::Id) -> Result<Option<User>, Self::Error> { ... }
async fn update(&self, entity: User) -> Result<User, Self::Error> { ... }
async fn delete(&self, id: Self::Id) -> Result<(), Self::Error> { ... }
async fn list(&self) -> Result<Vec<User>, Self::Error> { ... }
}
```
**`RepositoryError`** - Standard error type for repository operations
```rust
pub enum RepositoryError {
DatabaseError(sqlx::Error),
NotFound(String),
SerializationError(serde_json::Error),
InvalidData(String),
ConstraintViolation(String),
GenericError(anyhow::Error),
}
```
### Context Traits
**`AppContext`** - Application context trait for dependency injection
```rust
use systemprompt_traits::AppContext;
impl AppContext for MyAppContext {
fn config(&self) -> Arc<dyn ConfigProvider> { ... }
fn module_registry(&self) -> Arc<dyn ModuleRegistry> { ... }
fn database_handle(&self) -> Arc<dyn DatabaseHandle> { ... }
}
```
**`ConfigProvider`** - Configuration provider trait
```rust
impl ConfigProvider for Config {
fn get(&self, key: &str) -> Option<String> { ... }
fn database_url(&self) -> &str { ... }
fn system_path(&self) -> &str { ... }
fn jwt_secret(&self) -> &str { ... }
fn api_port(&self) -> u16 { ... }
}
```
**`ModuleRegistry`** - Module registry trait for dynamic module management
```rust
impl ModuleRegistry for MyModuleRegistry {
fn get_module(&self, name: &str) -> Option<Arc<dyn Module>> { ... }
fn list_modules(&self) -> Vec<String> { ... }
}
```
### Service Traits
**`Service`** - Base service trait with lifecycle methods
```rust
use systemprompt_traits::Service;
#[async_trait]
impl Service for MyService {
fn name(&self) -> &str { "my-service" }
async fn start(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { ... }
async fn stop(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> { ... }
async fn health_check(&self) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> { ... }
}
```
**`AsyncService`** - Async service trait for long-running background tasks
```rust
use systemprompt_traits::AsyncService;
#[async_trait]
impl AsyncService for MyAsyncService {
async fn run(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Long-running task
}
}
```
### Module Traits
**`Module`** - Core module trait for systemprompt.io modules
```rust
#[async_trait]
impl Module for MyModule {
fn name(&self) -> &str { "my-module" }
fn version(&self) -> &str { "1.0.0" }
fn display_name(&self) -> &str { "My Module" }
async fn initialize(&self) -> Result<(), Box<dyn std::error::Error>> { ... }
}
```
**`ApiModule`** - Module trait with REST API support
```rust
#[async_trait]
impl ApiModule for MyApiModule {
fn router(&self, ctx: Arc<dyn AppContext>) -> axum::Router { ... }
}
```
## Usage Patterns
### When to Use Traits vs Concrete Types
**Use Traits When:**
- You need dependency injection for testing
- You want to support multiple implementations
- You're defining interfaces between modules
- You need polymorphic behavior
**Use Concrete Types When:**
- Performance is critical and trait objects add overhead
- There's only one implementation
- The API is module-internal
- Type inference is important
### Testing with Traits
Traits enable easy mocking:
```rust
#[cfg(test)]
mod tests {
use super::*;
struct MockRepository {
pool: MockPool,
}
impl Repository for MockRepository {
type Pool = MockPool;
type Error = RepositoryError;
fn pool(&self) -> &Self::Pool { &self.pool }
}
#[tokio::test]
async fn test_with_mock() {
let repo = MockRepository { pool: MockPool::new() };
// Test using trait methods
}
}
```
### Error Handling
All repository errors automatically convert to `ApiError`:
```rust
use systemprompt_models::{ApiError, RepositoryError};
let result: Result<User, RepositoryError> = repo.get_user("id").await;
## Architecture
This crate follows the **Interface Segregation Principle** from SOLID:
- Traits are small and focused
- Clients depend only on the methods they use
- No fat interfaces or forced implementations
## Dependencies
Minimal dependencies to avoid circular deps:
- `async-trait` - Async trait support
- `anyhow` - Error handling
- `axum` - Router type for ApiModule
- `inventory` - Module registration
- `thiserror` - Error derive macros
- `sqlx` - Database types for Repository
- `serde_json` - Serialization errors
**No dependencies on other systemprompt.io crates** - this is intentional to prevent circular dependencies.
## Tool Provider Traits
### `ToolProvider` - Abstract tool discovery and execution
Enables modules to use tools without depending on specific implementations (e.g., MCP).
```rust
use systemprompt_traits::{ToolProvider, ToolContext, ToolDefinition};
#[async_trait]
impl ToolProvider for MyToolProvider {
async fn list_tools(&self, agent_name: &str, context: &ToolContext)
-> ToolProviderResult<Vec<ToolDefinition>> { ... }
async fn call_tool(&self, request: &ToolCallRequest, service_id: &str, context: &ToolContext)
-> ToolProviderResult<ToolCallResult> { ... }
async fn refresh_connections(&self, agent_name: &str) -> ToolProviderResult<()> { ... }
async fn health_check(&self) -> ToolProviderResult<HashMap<String, bool>> { ... }
}
```
Supporting types: `ToolDefinition`, `ToolCallRequest`, `ToolCallResult`, `ToolContent`, `ToolContext`, `ToolProviderError`
## LLM Provider Traits
### `LlmProvider` - Abstract LLM interactions
```rust
use systemprompt_traits::{LlmProvider, ChatRequest, ChatResponse};
#[async_trait]
impl LlmProvider for MyProvider {
async fn chat(&self, request: &ChatRequest) -> LlmProviderResult<ChatResponse> { ... }
async fn stream_chat(&self, request: &ChatRequest) -> LlmProviderResult<ChatStream> { ... }
fn default_model(&self) -> &str { "model-name" }
fn supports_model(&self, model: &str) -> bool { ... }
fn supports_streaming(&self) -> bool { true }
fn supports_tools(&self) -> bool { true }
}
```
### `ToolExecutor` - Execute tools during conversations
```rust
use systemprompt_traits::{ToolExecutor, ToolExecutionContext};
#[async_trait]
impl ToolExecutor for MyExecutor {
async fn execute(&self, tool_calls: Vec<ToolCallRequest>, tools: &[ToolDefinition],
context: &ToolExecutionContext) -> (Vec<ToolCallRequest>, Vec<ToolCallResult>) { ... }
}
```
Supporting types: `ChatMessage`, `ChatRole`, `ChatRequest`, `ChatResponse`, `SamplingParameters`, `TokenUsage`, `ToolExecutionContext`
## License
FSL-1.1-ALv2 - See [LICENSE](../../LICENSE) for details.