This file provides guidance to OpenAI Codex 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