- High-Level Architecture
- Core Design Philosophy
- The 22 Core Services
- Runtime Services from Adapters
- Message Bus Architecture
- Type Safety Architecture
- Async Design Patterns
- Graph Memory System
- SQLite Threading Model
- Initialization Flow
- Cognitive States
- Deployment Architecture
- 1000-Year Design
┌─────────────────────────────────────────────────────────────────────────────┐
│ CIRIS ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ ADAPTERS LAYER │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ Discord │ │ API │ │ CLI │ │ Future │ │ Future │ │ │
│ │ │ Adapter │ │ Adapter │ │ Adapter │ │ Medical │ │Education│ │ │
│ │ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ └────┬────┘ │ │
│ └───────┼────────────┼────────────┼────────────┼────────────┼────────┘ │
│ │ │ │ │ │ │
│ ┌───────▼────────────▼────────────▼────────────▼────────────▼────────┐ │
│ │ MESSAGE BUS LAYER │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │MemoryBus │ │ LLMBus │ │ ToolBus │ │CommBus │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ │ ┌──────────┐ ┌──────────┐ │ │
│ │ │ WiseBus │ │RuntimeBus│ │ │
│ │ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 22 SERVICES LAYER │ │
│ │ │ │
│ │ Graph Services (6) │ Runtime Services (3) │ │
│ │ ┌──────────────────┐ │ ┌─────────────┐ ┌──────────────┐ │ │
│ │ │ Memory Service │ │ │ LLM Service │ │Runtime Ctrl │ │ │
│ │ │ Audit Service │ │ │Task Sched │ │ │ │
│ │ │ Config Service │ │ └─────────────┘ └──────────────┘ │ │
│ │ │ Telemetry Service│ │ │ │
│ │ │ Incident Service │ │ Infrastructure Services (7) │ │
│ │ │ TSDB Service │ │ ┌─────────────┐ ┌──────────────┐ │ │
│ │ └──────────────────┘ │ │Time Service │ │Shutdown Svc │ │ │
│ │ │ │Init Service │ │Auth Service │ │ │
│ │ Governance (5) │ │Resource Mon │ │Database Maint│ │ │
│ │ ┌──────────────────┐ │ │Secrets Svc │ │ │ │
│ │ │ Wise Authority │ │ └─────────────┘ └──────────────┘ │ │
│ │ │ Adaptive Filter │ │ │ │
│ │ │ Visibility │ │ Tool Services (1) │ │
│ │ │ Self Observation │ │ ┌─────────────┐ │ │
│ │ │ Consent Service │ │ │ │
│ │ └──────────────────┘ │ │Secrets Tool │ │ │
│ │ │ └─────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ DATA PERSISTENCE LAYER │ │
│ │ ┌──────────────┐ ┌────────────────┐ ┌───────────────────────┐ │ │
│ │ │ SQLite DB │ │ Graph Memory │ │ Local File System │ │ │
│ │ │ (Identity) │ │ (In-Memory) │ │ (Config/Logs) │ │ │
│ │ └──────────────┘ └────────────────┘ └───────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
CIRIS follows the principle of "No Untyped Dicts, No Bypass Patterns, No Exceptions":
- No Untyped Dicts: Zero
Dict[str, Any]in production code. Everything is strongly typed with Pydantic models. - No Bypass Patterns: Every component follows consistent rules and validation patterns.
- No Exceptions: No special cases, emergency overrides, or privileged code paths.
- No Backwards Compatibility: The codebase moves forward only. Clean breaks over legacy support.
This philosophy ensures:
- Type Safety: Catch errors at development time, not runtime
- Self-Documenting: Types serve as inline documentation
- Refactoring Confidence: Change with certainty
- IDE Support: Full autocomplete and type checking
- Runtime Validation: Pydantic validates all data automatically
CIRIS has exactly 22 core services that are always present. These are the foundational services required for system operation.
These services manage different aspects of the graph memory system:
Purpose: Core graph operations and memory storage
Protocol: MemoryServiceProtocol
Bus: MemoryBus (supports multiple backends)
Why: Central to the "Graph Memory as Identity" architecture. All knowledge is stored as graph memories.
# Example: Storing a memory
await memory_bus.memorize(
concept="user_preference",
content="User prefers dark mode",
metadata={"confidence": 0.9}
)Purpose: Immutable audit trail with cryptographic signatures
Protocol: AuditServiceProtocol
Access: Direct injection
Why: Compliance, debugging, and trust. Every action leaves a permanent trace.
Purpose: Dynamic configuration stored in graph
Protocol: ConfigServiceProtocol
Access: Direct injection
Why: Configuration as memory - configs can evolve and be versioned like any other knowledge.
Purpose: Performance metrics and system health
Protocol: TelemetryServiceProtocol
Access: Direct injection
Why: Observability without external dependencies. Works offline.
Purpose: Track problems, incidents, and resolutions
Protocol: IncidentServiceProtocol
Access: Direct injection
Why: Learn from failures. Every incident becomes institutional memory.
Purpose: Consolidates telemetry into 6-hour summaries for permanent memory
Protocol: TSDBConsolidationServiceProtocol
Access: Direct injection
Why: Long-term memory (1000+ years). Raw data kept 24h, summaries forever.
Essential runtime services:
Purpose: Interface to language models (OpenAI, Anthropic, Mock)
Protocol: LLMServiceProtocol
Bus: LLMBus (supports multiple providers and fallbacks)
Why: Abstract LLM complexity. Support offline mode with mock LLM.
# Example: LLM with automatic fallback
response = await llm_bus.generate(
prompt="Explain quantum computing",
max_tokens=150,
temperature=0.7
)Purpose: Dynamic system control (pause/resume processor, adapter management)
Protocol: RuntimeControlProtocol
Bus: RuntimeControlBus (always present)
Why: Remote management and debugging. Essential for production operations.
Purpose: Cron-like task scheduling and agent self-directed activities
Protocol: TaskSchedulerProtocol
Access: Direct injection
Why: Autonomous operation. Enables proactive agent behavior and maintenance.
Foundation services that enable the system:
Purpose: Consistent time operations across the system
Protocol: TimeServiceProtocol
Access: Direct injection
Why: Testability and consistency. No direct datetime.now() calls.
Purpose: Graceful shutdown coordination
Protocol: ShutdownServiceProtocol
Access: Direct injection
Why: Data integrity. Ensure clean shutdown even in resource-constrained environments.
Purpose: Startup orchestration and dependency management
Protocol: InitializationServiceProtocol
Access: Direct injection
Why: Complex initialization order. Services have interdependencies.
Purpose: Identity verification and access control
Protocol: AuthServiceProtocol
Access: Direct injection
Why: Multi-tenant support. Different users/organizations in same deployment.
Purpose: Track CPU, memory, disk usage
Protocol: ResourceMonitorProtocol
Access: Direct injection
Why: Prevent resource exhaustion in constrained environments (4GB RAM target).
Purpose: SQLite optimization, vacuum operations, and long-term health
Protocol: DatabaseMaintenanceProtocol
Access: Direct injection
Why: Critical for 1000-year operation in resource-constrained environments.
Purpose: Cryptographic secret management and encryption
Protocol: SecretsServiceProtocol
Access: Direct injection
Why: Central security boundary. All secrets encrypted with AES-256-GCM.
Purpose: User consent management and privacy compliance
Protocol: ConsentServiceProtocol
Access: Direct injection
Why: GDPR compliance and ethical data handling. Manages TEMPORARY (14-day), PARTNERED (bilateral), and ANONYMOUS consent streams. Coordinates with modular DSAR services for external data compliance.
Ethical and operational governance:
Purpose: Ethical decision making and guidance
Protocol: WiseAuthorityProtocol
Bus: WiseBus (supports distributed wisdom)
Why: Ubuntu philosophy. Decisions consider community impact.
Purpose: Intelligent message prioritization and spam detection
Protocol: AdaptiveFilterProtocol
Access: Direct injection
Why: Manages attention economy. Learns what deserves immediate response vs. deferral. Tracks user trust levels.
Purpose: Reasoning transparency - the "why" behind decisions (TRACES)
Protocol: VisibilityServiceProtocol
Access: Direct injection
Why: Trust through transparency. Explains decision chains, not system metrics.
Purpose: Behavioral analysis and pattern detection that generates insights
Protocol: SelfObservationProtocol
Access: Direct injection
Why: Continuous learning. Detects patterns, generates insights the agent can act on. Monitors identity variance and triggers WA review if threshold exceeded.
Purpose: Agent self-help tools including secret recall and filter updates
Protocol: ToolServiceProtocol
Bus: ToolBus (always present)
Why: Enables agent self-sufficiency. Core tools always available even offline.
Beyond the 22 core services, CIRIS supports modular tool services that can be dynamically loaded at runtime. These are optional services that extend functionality without being part of the core system.
Key Principles:
- Tools, Not Services - External data sources are modeled as tools on ToolBus
- Privacy Schema-Driven - All operations governed by privacy schema configuration
- Multi-Connector Pattern - Single service can manage multiple data source connectors
- Runtime Configuration - Connectors can be added/removed without code changes
Purpose: DSAR/GDPR compliance for external SQL databases
Protocol: SQLDataSourceProtocol (implements ToolServiceProtocol)
Bus: ToolBus (dynamic tool registration)
Location: ciris_adapters/external_data_sql/
Architecture Patterns:
-
Privacy Schema as First-Class Citizen
- Declarative mapping of tables, columns, and PII types
- Configurable anonymization strategies per column
- Cascade deletion rules for related data
- YAML-based configuration with runtime loading
-
Runtime Configuration
- Dynamic connector initialization via
initialize_sql_connectortool - Metadata discovery via
get_sql_service_metadatatool - No service restart required for configuration changes
- Multiple connectors via connector_id routing parameter
- Dynamic connector initialization via
-
Generic Tool Naming
- 9 tools with generic
sql_*prefix (not connector-specific) - Connector routing via
connector_idparameter - Tools:
initialize_sql_connector,get_sql_service_metadata,sql_find_user_data,sql_export_user,sql_delete_user,sql_anonymize_user,sql_verify_deletion,sql_get_stats,sql_query - Service validates connector_id matches its configured instance
- 9 tools with generic
-
Tool Bus Integration
- Tools discovered via ToolBus capability matching:
tool:sql - No service registry lookup - tools are interface
- Follows existing CIRIS tool service patterns
- Tools discovered via ToolBus capability matching:
-
Supported Dialects
- SQLite (file-based, serverless)
- MySQL (open-source RDBMS)
- PostgreSQL (advanced RDBMS)
- Dialect-aware query generation with specialized implementations
DSAR Compliance Features:
- Access (GDPR Art. 15): Export user data in JSON/CSV via
sql_export_user - Erasure (GDPR Art. 17): Delete with cascade and crypto verification via
sql_delete_user - Portability (GDPR Art. 20): Structured data export with checksums
- Verification: Post-deletion zero-data confirmation with Ed25519 signatures via
sql_verify_deletion - Discovery: Find all user data locations via
sql_find_user_data - Anonymization: Privacy-preserving alternative to deletion via
sql_anonymize_user
Security Model:
- Privacy schema required for DSAR operations
- Raw SQL queries constrained by privacy schema (SELECT only)
- Transaction-based delete/anonymize operations
- Connection strings stored in SecretsService
- WiseAuthority approval for sensitive operations
- Runtime reconfiguration with validation
- OpenAPI Tool Service - SaaS integration via OpenAPI specs
- Graph Query Service - External graph database connectors
- File System Service - Local file-based data access
- Cloud Storage Service - S3/GCS/Azure Blob integration
When adapters are loaded at runtime, they register additional services with the system. Each adapter typically provides 3 services:
Discord Adapter:
- Communication Service (handles Discord messages)
- Wise Authority Service (Discord user-based WA)
- Tool Service (Discord-specific tools)
API Adapter:
- Communication Service (HTTP request handling)
- Runtime Control Service (API-based system control)
- Tool Service (API-specific tools)
CLI Adapter:
- Communication Service (terminal I/O)
- Tool Service (CLI-specific tools)
- Optional: WA Service (for interactive guidance)
With all adapters loaded:
- 22 core services (always present)
- Up to 9 adapter services (3 per adapter)
- Total: ~31 services in a fully loaded system
Note: Services are registered with unique IDs, so multiple instances of the same adapter type can coexist.
CIRIS uses 6 message buses for services that support multiple providers:
Buses provide:
- Provider Abstraction: Swap implementations without changing code
- Fallback Support: Automatic failover to backup providers
- Load Distribution: Spread work across multiple providers
- Testing: Easy mock provider injection
Providers: Neo4j, ArangoDB, In-Memory, SQLite (future) Purpose: Abstract graph backend differences Example:
# Works with any graph backend
result = await memory_bus.search(
SearchParams(
query="vaccination schedule",
max_results=10,
scope=GraphScope.LOCAL
)
)Providers: OpenAI, Anthropic, Llama, Mock Purpose: Model abstraction and fallback Example:
# Automatic provider selection based on availability
response = await llm_bus.generate(
prompt=prompt,
model_preferences=["gpt-4", "claude-3", "llama-70b", "mock"]
)Providers: Adapter-specific tools + core secrets tools + modular tool services Purpose: Dynamic tool discovery and execution Note: Core secrets tool service always present, adapters and modular services (e.g., SQL external data) add more at runtime
Providers: Discord, API, CLI adapters Purpose: Unified communication interface Note: No standalone service - adapters provide communication
Providers: Local WA, Distributed WAs, Consensus Purpose: Ethical guidance with fallback Example:
# Get wisdom from available sources
guidance = await wise_bus.seek_wisdom(
situation="Patient refuses treatment",
context={"age": 14, "condition": "serious"}
)Providers: Core runtime control + optional adapter additions Purpose: System management interface Note: Core service always present, adapters may add additional providers
Use Bus When:
- Multiple providers possible (LLM, Memory, WiseAuthority)
- Provider might change at runtime
- Fallback behavior needed
- Adapter provides the service (Tool, Communication, RuntimeControl)
Use Direct Injection When:
- Single instance by design (Time, Config, Audit)
- Infrastructure service (Shutdown, Init, Auth)
- Performance critical (no bus overhead)
CIRIS uses a two-tier telemetry architecture that balances type safety with implementation simplicity:
All 6 message buses return typed BusMetrics (Pydantic models) instead of untyped dicts. This ensures type safety for the complex routing and provider selection logic that buses handle.
from ciris_engine.schemas.infrastructure.base import BusMetrics
def get_metrics(self) -> BusMetrics:
"""Get bus metrics as typed BusMetrics schema."""
return BusMetrics(
messages_sent=self._sent_count,
messages_received=self._received_count,
messages_dropped=0,
average_latency_ms=0.0,
active_subscriptions=len(self._subscribers),
queue_depth=self.get_queue_size(),
errors_last_hour=self._error_count,
busiest_service=None,
additional_metrics={
"bus_specific_metric": self._custom_value,
},
)Services return simple Dict[str, float] which the telemetry service converts to ServiceTelemetryData. This keeps service implementations lightweight while maintaining type safety at the collection boundary.
async def get_metrics(self) -> Dict[str, float]:
"""Return service metrics as dict."""
return {
"uptime_seconds": uptime,
"requests_handled": float(self._requests),
"error_count": float(self._errors),
"error_rate": self._calculate_error_rate(),
"incidents_active": float(len(self._active_incidents)),
}┌─────────────────────────────────────────────────────────────┐
│ Telemetry Service │
│ (Central Collector) │
└─────────────────────────────────────────────────────────────┘
▲
│
┌────────────────┴────────────────┐
│ │
┌────▼─────┐ ┌─────▼────┐
│ Buses │ │ Services │
│ (Tier 1)│ │ (Tier 2) │
└────┬─────┘ └─────┬────┘
│ │
│ BusMetrics │ Dict[str,float]
│ (Pydantic) │
│ │
└────────────────┬─────────────────┘
│
▼
┌────────────────────┐
│ ServiceTelemetryData│
│ (Pydantic) │
└────────────────────┘
Buses need strict typing because they:
- Route between multiple providers
- Handle complex operational metrics
- Expose public APIs for monitoring
- Need validation for distributed systems
Services use simple dicts because they:
- Have straightforward metrics (counters, gauges)
- Benefit from lightweight implementations
- Are converted centrally by telemetry service
- Don't need complex validation logic
The telemetry service tries these methods in priority order:
async def get_metrics(self) -> Dict[str, float]← Preferreddef _collect_metrics(self) -> Dict[str, float]← Fallbackdef get_status(self) -> ServiceStatus← Last resort
For detailed telemetry implementation patterns, see:
ciris_engine/logic/services/graph/telemetry_service/TELEMETRY_ARCHITECTURE.mdciris_engine/logic/services/graph/telemetry_service/BEST_PRACTICES.md
CIRIS achieves zero Dict[str, Any] through comprehensive type safety:
All graph nodes extend TypedGraphNode:
@register_node_type("CONFIG")
class ConfigNode(TypedGraphNode):
"""Configuration stored as graph memory."""
key: str = Field(..., description="Config key")
value: ConfigValue = Field(..., description="Typed config value")
# Required fields
created_at: datetime
updated_at: datetime
created_by: str
def to_graph_node(self) -> GraphNode:
"""Convert to generic GraphNode for storage."""
# Implementation
@classmethod
def from_graph_node(cls, node: GraphNode) -> "ConfigNode":
"""Reconstruct from generic GraphNode."""
# Implementationschemas/
├── actions/ # Shared action parameters
│ ├── speak.py # SpeakParams used by handlers, DMAs, services
│ ├── memorize.py # MemorizeParams
│ └── search.py # SearchParams
├── services/ # Service-specific schemas
│ ├── nodes.py # All TypedGraphNode definitions
│ ├── graph_core.py # Base graph types
│ └── llm_core.py # LLM schemas
└── core/ # Core system schemas
├── identity.py # Agent identity
└── profiles.py # Agent profiles
Pydantic validates at boundaries:
# Bad - Dict[str, Any]
def process_config(config: dict) -> dict:
value = config.get("key", "default")
return {"result": value}
# Good - Typed schemas
def process_config(config: ConfigNode) -> ConfigResult:
# config.key is guaranteed to exist and be a string
# config.value is guaranteed to be a ConfigValue
return ConfigResult(
key=config.key,
processed_value=transform(config.value)
)CIRIS uses async/await throughout for efficiency in resource-constrained environments:
All services are async-first:
class MemoryService(ServiceProtocol):
async def start(self) -> None:
"""Async startup allows concurrent initialization."""
await self._init_graph_connection()
await self._load_initial_memories()
async def memorize(self, params: MemorizeParams) -> MemorizeResult:
"""Non-blocking memory storage."""
async with self._graph_lock:
node = await self._create_node(params)
await self._create_relationships(node)
return MemorizeResult(success=True, node_id=node.id)Resource management with async context:
class GraphConnection:
async def __aenter__(self):
await self.connect()
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
await self.disconnect()
# Usage
async with GraphConnection() as graph:
await graph.query("MATCH (n) RETURN n LIMIT 10")Efficient parallel execution:
# Bad - Sequential
results = []
for query in queries:
result = await llm_bus.generate(query)
results.append(result)
# Good - Concurrent
results = await asyncio.gather(*[
llm_bus.generate(query) for query in queries
])- Fire and Forget: For non-critical operations
asyncio.create_task(audit_service.log_event(event))- Timeout Protection: Prevent hanging
try:
result = await asyncio.wait_for(
llm_bus.generate(prompt),
timeout=30.0
)
except asyncio.TimeoutError:
result = await fallback_response()- Async Iteration: For streaming responses
async for chunk in llm_bus.stream_generate(prompt):
await process_chunk(chunk)The graph memory system is central to CIRIS's identity architecture:
All data is stored as graph nodes with relationships:
User Preference ──[RELATES_TO]──> Dark Mode Setting
│
└──[LEARNED_AT]──> Timestamp Node
│
└──[CONFIDENCE]──> 0.9
11 active TypedGraphNode classes:
- IdentityNode: Core agent identity (agent/identity)
- ConfigNode: System configuration
- AuditEntry: Immutable audit trail
- IncidentNode: System incidents
- ProblemNode: Detected problems
- IncidentInsightNode: Learned insights
- TSDBSummary: Time-series summaries
- IdentitySnapshot: Agent identity versions
- DiscordDeferralNode: Discord deferral tracking
- DiscordApprovalNode: WA approval tracking
- DiscordWANode: Wise Authority assignments
# Store a memory with relationships
memory_result = await memory_bus.memorize(
MemorizeParams(
concept="patient_visit",
content="Patient ABC123 visited for vaccination",
metadata={
"patient_id": "ABC123",
"visit_type": "vaccination",
"timestamp": time_service.now()
},
scope=GraphScope.LOCAL,
associations=[
Association(
target_concept="patient_record",
relationship="VISIT_FOR",
metadata={"visit_id": "V456"}
)
]
)
)
# Search memories with context
results = await memory_bus.search(
SearchParams(
query="vaccination history ABC123",
include_associations=True,
max_depth=2,
filters={"visit_type": "vaccination"}
)
)- Correlation Discovery: Find hidden relationships
- Pattern Mining: Identify recurring structures
- Temporal Analysis: How memories change over time
- Confidence Scoring: Weight memories by reliability
- Scope Isolation: Separate local/global knowledge
CIRIS uses SQLite with careful threading design for offline operation:
- Offline-First: No network dependency
- Low Resource: Minimal RAM usage
- Reliable: Battle-tested in billions of devices
- Portable: Single file database
- Concurrent Reads: Multiple readers, single writer
class DatabaseManager:
def __init__(self):
# One connection per thread
self._thread_local = threading.local()
self._write_lock = asyncio.Lock()
def get_connection(self):
if not hasattr(self._thread_local, 'conn'):
self._thread_local.conn = sqlite3.connect(
self.db_path,
check_same_thread=False,
isolation_level='IMMEDIATE'
)
return self._thread_local.conn
async def write_operation(self, query, params):
async with self._write_lock:
conn = self.get_connection()
await asyncio.to_thread(
self._execute_write, conn, query, params
)- Connection Per Thread: Avoid sharing connections
- Write Serialization: One writer at a time
- Read Parallelism: Multiple concurrent readers
- Short Transactions: Minimize lock time
- WAL Mode: Better concurrency
# Enable WAL mode for better concurrency
conn.execute("PRAGMA journal_mode=WAL")
conn.execute("PRAGMA synchronous=NORMAL")CIRIS follows a strict initialization order to manage dependencies:
1. INFRASTRUCTURE
├── TimeService (everyone needs time)
├── ShutdownService (cleanup coordination)
├── InitializationService (tracks startup)
└── ResourceMonitor (prevent exhaustion)
2. DATABASE
└── SQLite initialization with migrations
3. MEMORY FOUNDATION
├── SecretsService (memory needs auth)
└── MemoryService (core graph operations)
4. IDENTITY
└── Load/create agent identity from DB
5. GRAPH SERVICES
├── ConfigService (uses memory)
├── AuditService (uses memory)
├── TelemetryService (uses memory)
├── IncidentService (uses memory)
└── TSDBService (uses memory)
6. SECURITY
└── WiseAuthorityService (needs identity)
7. REMAINING SERVICES
├── LLMService
├── AuthenticationService
├── DatabaseMaintenanceService
├── RuntimeControlService
├── TaskSchedulerService
├── AdaptiveFilterService
├── VisibilityService
└── SelfObservationService
8. TOOL SERVICES
└── SecretsToolService
9. COMPONENTS
├── Build processors
├── Build handlers
└── Wire dependencies
10. VERIFICATION
└── Final health checks
- No Circular Dependencies: Services cannot depend on each other circularly
- Explicit Dependencies: Constructor injection only
- No Runtime Lookup: No
service_registry.get()in production code - Single Creator: Only ServiceInitializer creates services
CIRIS operates in 6 distinct cognitive states:
Purpose: Identity confirmation and system check Activities:
- Confirm "I am CIRIS"
- Load identity from database
- Verify all services healthy
- Establish purpose
Purpose: Normal task processing Activities:
- Handle user requests
- Execute tools
- Learn from interactions
- Maintain conversation context
Purpose: Creative exploration Activities:
- Experiment with new patterns
- Generate creative content
- Explore "what if" scenarios
- Lower filtering constraints
Purpose: Reflection and maintenance Activities:
- Consolidate memories
- Run maintenance tasks
- Update self-configuration
- Process accumulated insights
Purpose: Deep introspection Activities:
- Analyze behavior patterns
- Generate new connections
- Question assumptions
- Simulate scenarios
Purpose: Graceful termination Activities:
- Save critical state
- Close connections cleanly
- Final audit entries
- Farewell message
STARTUP ──> WAKEUP ──> WORK ←──→ PLAY
↑ ↓ ↓
└──── SOLITUDE ←─────┘
↓
DREAM
↓
SHUTDOWN
CIRIS supports multiple deployment scenarios:
python main.py --adapter cli --template datum --mock-llm- Single process
- In-memory graph
- Mock LLM for offline testing
- SQLite for identity
python main.py --adapter discord --template ubuntu- Real LLM providers with fallback
- Persistent graph storage
- Full audit trail
- Resource monitoring
python main.py --adapter api --host 0.0.0.0 --port 8080- RESTful API
- OAuth2 authentication
- Multi-tenant support
- Horizontal scaling ready
version: '3.8'
services:
ciris:
image: ciris:latest
environment:
- CIRIS_ADAPTER=api
- CIRIS_TEMPLATE=datum
- OPENAI_API_KEY=${OPENAI_API_KEY}
volumes:
- ./data:/app/data
ports:
- "8080:8080"- Raspberry Pi in rural clinic
- 4GB RAM constraint
- Intermittent connectivity
- Local language models
CIRIS is designed to operate for 1000 years through:
- No external dependencies for core function
- Offline-first architecture
- Local data persistence
- Embedded documentation
- Configuration as code
- Self-optimization service
- Adaptation proposals
- Learning from usage
- Ubuntu philosophy core
- Community-first decisions
- Local context awareness
- Multi-language support (future)
- Every decision recorded
- Insights extracted from incidents
- Knowledge accumulation
- Pattern recognition
- Fallback providers
- Mock mode for offline
- Resource constraints handled
- Progressive enhancement
# Rural clinic configuration
config = {
"deployment_context": "rural_medical",
"constraints": {
"max_memory_gb": 4,
"network": "intermittent",
"power": "unreliable"
},
"priorities": [
"patient_safety",
"data_sovereignty",
"cultural_sensitivity"
],
"language": "swahili",
"llm_preferences": ["local_llama", "mock"],
"sync_strategy": "opportunistic"
}The system adapts:
- Uses local Llama model
- Syncs when network available
- Preserves battery during outages
- Respects local medical practices
- Maintains audit trail always
Key decisions that shaped CIRIS:
- SQLite over PostgreSQL: Offline-first requirement
- 22 Core Services + Modular Services: Fixed core (22) with optional modular extensions via ToolBus
- Pydantic Everywhere: Runtime validation critical for medical use
- Graph Memory: Flexible knowledge representation
- Mock LLM: Essential for offline operation
- No Backwards Compatibility: Clean evolution
- Ubuntu Philosophy: Culturally appropriate for target deployments
- Privacy Schema-Driven: External data operations governed by declarative privacy schemas
- Tools Over Services: External data sources as tools on ToolBus, not new service types
- Define the protocol in
protocols/services/ - Implement service in
logic/services/ - Create schemas in
schemas/services/ - Add to ServiceInitializer
- Update service count documentation
- Add tests
- Create TypedGraphNode subclass
- Add @register_node_type decorator
- Implement to_graph_node/from_graph_node
- Add to nodes.py
- Update documentation
- Create directory in
ciris_adapters/ - Define manifest.json with service metadata
- Implement protocol extending
ToolServiceProtocol(for tools) - Create Pydantic schemas (no Dict[str, Any])
- Implement service with privacy schema support (if applicable)
- Register tools with ToolBus during initialization
- Add tests and documentation
- Integration over unit tests
- Real schemas, no dict mocks
- Test through protocols
- Async test patterns
- Offline scenarios
CIRIS's architecture may seem over-engineered for a Discord bot, but it's designed for a larger purpose: bringing AI assistance to resource-constrained environments where it's needed most. Every architectural decision supports the goal of reliable, ethical, offline-capable AI that can run for 1000 years.
The type safety ensures medical-grade reliability. The service architecture enables deployment flexibility. The graph memory creates institutional knowledge. The offline-first design serves communities without reliable internet. The Ubuntu philosophy ensures culturally appropriate behavior.
This is not just a chatbot. It's infrastructure for human flourishing.