This file provides guidance to AI assistants (like Aider, Continue.dev, Sourcegraph Cody, etc.) when working with code in this repository.
Based on issues discovered during code reviews, the following general development standards have been compiled:
- MUST define a unified error type in the crate root (e.g.,
KernelError) - MUST establish a clear error hierarchy where module errors can be unified through the
Fromtrait - MUST NOT use
anyhow::Resultas a public API return type in library code; usethiserrorto define typed errors - MUST NOT implement blanket
From<anyhow::Error>for error types, as this erases structured error information
// Recommended: Use thiserror to define clear error enums
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum KernelError {
#[error("Agent error: {0}")]
Agent(#[from] AgentError),
#[error("Config error: {0}")]
Config(#[from] ConfigError),
// ...
}- MUST add the
#[non_exhaustive]attribute to public enums to ensure backward compatibility
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum AgentState {
Idle,
Running,
// New variants can be safely added in the future
}- Comparable/testable types MUST derive
PartialEq,Eq - Debug output types MUST derive
Debug; for fields that cannot be auto-derived, implement manually - Serializable types MUST derive
Clone(unless there's a special reason not to)
- MUST NOT define types with the same name representing different concepts within the same crate
- Checklist:
AgentConfig,AgentEvent,TaskPriority, and other core type names
- MUST use
pub(crate)to limit internal module visibility - MUST carefully design the public API surface through
lib.rsorprelude - MUST NOT directly
pub modexport all modules
// Recommended lib.rs structure
pub mod error;
pub mod agent;
pub use error::KernelError;
pub use agent::{Agent, AgentContext};
mod internal; // Internal implementation- SHOULD provide a crate-level prelude module that aggregates commonly used types
// src/prelude.rs
pub use crate::error::KernelError;
pub use crate::agent::{Agent, AgentContext, AgentState};
// ...- In Rust 1.75+ environments, SHOULD use native
async fn in traitinstead of#[async_trait] - Only use
asyncon methods that genuinely require async; synchronous operations should not be marked as async
- Objects with high compilation costs like regular expressions MUST be cached using
LazyLockorOnceLock
use std::sync::LazyLock;
static ENV_VAR_REGEX: LazyLock<Regex> = LazyLock::new(|| {
Regex::new(r"\$\{([^}]+)\}").unwrap()
});- Timestamp generation logic MUST be abstracted into a single utility function
- SHOULD provide an injectable clock abstraction for testing
pub trait Clock: Send + Sync {
fn now_millis(&self) -> u64;
}
pub struct SystemClock;
impl Clock for SystemClock {
fn now_millis(&self) -> u64 {
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_millis() as u64
}
}- MUST NOT hand-write logic for Base64, encryption algorithms, etc. that have mature implementations
- Prioritize using widely validated community crates
- MUST NOT abuse
serde_json::Valuein scenarios where generic constraints can be used - AVOID using
Box<dyn Any + Send + Sync>as generic storage; prefer generics or trait objects with specific traits
- The choice between
&selfand&mut selfin trait method signatures MUST be consistent - If internal state modification through
&selfis needed (e.g.,Arc<RwLock<_>>), document side effects clearly
- Constructor parameter types SHOULD be unified: prefer
impl Into<String>or&str
// Recommended
pub fn new(id: impl Into<String>) -> Self { ... }
// Avoid
pub fn new(id: String) -> Self { ... }- Builder methods MUST validate invalid input or return
Result
pub fn with_weight(mut self, weight: f64) -> Result<Self, &'static str> {
if weight < 0.0 {
return Err("Weight must be non-negative");
}
self.weight = Some(weight);
Ok(self)
}- MUST NOT create custom method names that conflict with standard trait method names (e.g.,
to_string_outputvsto_string)
- MUST write complete tests covering all branches for manually implemented
Ordtrait - Recommend using
deriveor simplified implementations based on discriminants
- Numeric type conversions MUST explicitly handle potential overflow
// Avoid
let ts = as_millis() as u64;
// Recommended
let ts = u64::try_from(as_millis()).unwrap_or(u64::MAX);- Binary serialization MUST include version identifiers
#[derive(Serialize, Deserialize)]
struct MessageEnvelope {
version: u8,
payload: Vec<u8>,
}- Message buses SHOULD support pluggable serialization backends
pub trait Serializer: Send + Sync {
fn serialize<T: Serialize>(&self, value: &T) -> Result<Vec<u8>>;
fn deserialize<T: DeserializeOwned>(&self, data: &[u8]) -> Result<T>;
}- MUST include: boundary values, null values, invalid input, concurrent scenarios
- MUST NOT only test the happy path
- MUST write unit tests for core logic
- SHOULD write integration tests for inter-module interactions
- External dependencies (clock, random numbers, network) MUST be injectable through traits for mock implementations
- Dependencies behind feature gates MUST be marked with
optional = trueinCargo.toml - MUST NOT feature gate partial code while the dependency is still compiled unconditionally
[dependencies]
config = { version = "0.14", optional = true }
[features]
default = []
config-loader = ["dep:config"]| Check Item | Requirement | Status |
|---|---|---|
Public enums have #[non_exhaustive] |
Must | ☐ |
| Public error types are unified | Must | ☐ |
| No types with same name but different meanings | Forbidden | ☐ |
| Traits have unnecessary async usage | Check | ☐ |
| Numeric conversions have overflow risk | Check | ☐ |
| Time-related code is testable | Must | ☐ |
| Builders have input validation | Must | ☐ |
| Regex etc. use caching | Must | ☐ |
| Integration tests exist | Should | ☐ |
| Error path test coverage | Must | ☐ |
MoFA follows a strict microkernel architecture with clear separation of concerns:
┌─────────────────────────────────────────────────────────────┐
│ mofa-sdk (Standard API) │
│ - External standard interface │
│ - Re-exports core types from kernel and foundation │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ mofa-runtime (Execution Lifecycle) │
│ - AgentRegistry, EventLoop, PluginManager │
│ - Dynamic loading and plugin management │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ mofa-foundation (Business Logic) │
│ - ✅ Concrete implementations (InMemoryStorage, SimpleToolRegistry) |
│ - ✅ Extended types (RichAgentContext, business-specific data) |
│ - ❌ FORBIDDEN: Re-defining kernel traits │
│ - ✅ ALLOWED: Importing and extending kernel traits │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ mofa-kernel (Microkernel Core) │
│ - ✅ Trait definitions (Tool, Memory, Reasoner, etc.) │
│ - ✅ Core data types (AgentInput, AgentOutput, AgentState) │
│ - ✅ Base abstractions (MoFAAgent, AgentPlugin) │
│ - ❌ FORBIDDEN: Concrete implementations (except test code) │
│ - ❌ FORBIDDEN: Business logic │
└─────────────────────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────────┐
│ mofa-plugins (Plugin Layer) │
│ - Plugin adapters (ToolPluginAdapter) │
│ - Concrete plugin implementations │
└─────────────────────────────────────────────────────────────┘
- ✅ Kernel Layer: Define ALL core trait interfaces
- ❌ Foundation Layer: NEVER re-define the same trait from kernel
- ✅ Foundation Layer: CAN import traits from kernel and add extension methods
- ✅ Foundation Layer: Provide ALL concrete implementations
- ❌ Kernel Layer: NO concrete implementations (test code excepted)
- ✅ Plugins Layer: Provide optional advanced implementations
- ✅ Kernel: Export only types it defines
- ✅ Foundation: Export only types it implements, NOT re-export kernel traits
- ✅ SDK: Standard re-export of user-facing APIs
- ✅ Kernel Layer: Base data types (AgentInput, AgentOutput, AgentState, ToolInput, ToolResult)
- ✅ Foundation Layer: Business-specific data types (Session, PromptContext, ComponentOutput)
⚠️ Boundary: If a type is part of a trait definition, put it in kernel; if business-specific, put it in foundation
Foundation → Kernel (ALLOWED)
Plugins → Kernel (ALLOWED)
Plugins → Foundation (ALLOWED)
Kernel → Foundation (FORBIDDEN! Creates circular dependency)
| What | Where | Example |
|---|---|---|
| Trait definitions | mofa-kernel |
Tool, Memory, Reasoner, Coordinator |
| Core data types | mofa-kernel |
AgentInput, AgentOutput, AgentState |
| Base abstractions | mofa-kernel |
MoFAAgent, AgentPlugin |
| Concrete implementations | mofa-foundation |
SimpleToolRegistry, InMemoryStorage |
| Business types | mofa-foundation |
Session, PromptContext, RichAgentContext |
| Plugin implementations | mofa-plugins |
ToolPluginAdapter, LLMPlugin |
English is the primary language for all code comments and documentation.
- Use English for inline comments, doc comments (
///,//!), and README files - Variable, function, and type names should be in English
- Commit messages should be written in English
- This ensures consistency and accessibility for international contributors