Enki’s testing strategy focuses on inline unit and integration tests within the core crate, where the bulk of the orchestration logic lives. The synchronous state machine design makes the code highly testable.
The enki-core crate contains the majority of tests:
# Run all core testscargo test -p enki-core# Run tests with output visiblecargo test -p enki-core -- --nocapture# Run a specific testcargo test -p enki-core test_orchestrator_spawn_worker# Run tests matching a patterncargo test -p enki-core orchestrator
# Test the ACP clientcargo test -p enki-acp# Test the TUI librarycargo test -p enki-tui# Test the CLI binarycargo test -p enki-cli# Run all tests in the workspacecargo test
#[test]fn test_dag_edge_conditions() { let mut dag = Dag::new(); // Add nodes with dependency dag.add_node("step1", task1); dag.add_node("step2", task2); dag.add_edge("step2", "step1", EdgeCondition::Started); // Mark step1 as Running dag.transition("step1", NodeStatus::Running); // step2 should now be ready (Started condition satisfied) let ready = dag.get_ready_nodes(); assert!(ready.contains(&"step2"));}
#[test]fn test_signal_file_processing() { let temp_dir = tempdir().unwrap(); let events_dir = temp_dir.path().join("events"); fs::create_dir(&events_dir).unwrap(); // Write a signal file let signal = Signal::ExecutionCreated { execution_id: "exec-123".into(), }; let signal_path = events_dir.join("sig-test.json"); fs::write(&signal_path, serde_json::to_string(&signal).unwrap()).unwrap(); // Process signals let mut orch = Orchestrator::new_with_dir(db, temp_dir.path()); let events = orch.handle(Command::CheckSignals); // Verify signal was processed and file removed assert!(!signal_path.exists()); assert!(events.iter().any(|e| matches!(e, Event::StatusMessage(_))));}
The two-phase completion pattern is critical: WorkerDone (frees tier slot) → merge runs → MergeDone (advances DAG). Tests must verify both phases.
#[test]fn test_two_phase_completion() { let mut scheduler = Scheduler::new(limits); // Spawn worker, fills a tier slot scheduler.handle_spawn(task_id, Tier::Standard); assert_eq!(scheduler.active_count(Tier::Standard), 1); // Worker finishes → WorkerDone event scheduler.handle_worker_done(task_id); // Tier slot should be freed immediately assert_eq!(scheduler.active_count(Tier::Standard), 0); // But DAG should NOT advance until MergeDone assert_eq!(dag.get_status(step_id), NodeStatus::WorkerDone); // Send MergeDone scheduler.handle_merge_done(task_id); // Now DAG advances assert_eq!(dag.get_status(step_id), NodeStatus::Done);}
When debugging issues, always start with the logs:
# View the most recent sessiontail -n 200 ~/.enki/logs/enki.log# Find errors and warningsgrep "ERROR\|WARN" ~/.enki/logs/enki.log# View a specific agent sessioncat ~/.enki/logs/sessions/<label>.log
Logs are structured with clear session boundaries:
# Open the databasesqlite3 .enki/db.sqlite# Check task statusesSELECT id, title, status, tier FROM tasks;# View executionsSELECT id, status, dag FROM executions;# Check merge queueSELECT id, task_id, branch, status FROM merge_requests;
# Check copy pathsls -la .enki/copies/# On macOS with APFS, clonefile should be instantdu -sh .enki/copies/task-*# On Linux with btrfs, check reflink statusfilefrag -v .enki/copies/task-*/somefile
Problem: Trying to send ACP types across threadsSymptom: Compiler error about Rc<RefCell<...>> not implementing SendSolution: All ACP code must run on the coordinator’s LocalSet. Never spawn ACP types onto the tokio runtime directly.
Problem: Copy operations failing, all subsequent spawns auto-failSymptom: All tasks fail immediately without trying to spawnDebugging:
# Check disk spacedf -h# Verify CoW support# macOS: should use APFSdiskutil list# Linux: should use btrfs or xfsdf -T
Solution: Fix the underlying filesystem issue. The infra_broken flag prevents cascading failures.
process_events cascade not draining
Problem: Events pile up without being processedSymptom: Tasks stuck in Pending when they should spawnDebugging: Check coordinator’s event processing loop:
while !events.is_empty() { for event in events.drain(..) { // Process each event let new_events = process_event(event); events.extend(new_events); }}
Solution: Ensure the coordinator drains events in a loop, since spawning a worker can fail and produce new events.
Merge conflicts not resolving
Problem: Merger agent not spawning or failing silentlySymptom: MergeNeedsResolution event but no merger activityDebugging: