Use when adding a new harness adapter or modifying an existing one in hom-adapters
Invoke this skill when:
crates/hom-adapters/src/build_command, translate_input, parse_screen, or detect_completionHarnessAdapter trait in hom-coreEvery adapter implements HarnessAdapter from crates/hom-core/src/traits.rs. This trait is the single integration point between HOM and a harness. Breaking it breaks everything.
| Method | Purpose | Criticality |
|---|---|---|
harness_type() | Return the HarnessType enum variant |
| Must match registry |
display_name() | Human-readable name for status rail | Cosmetic |
build_command() | Construct the spawn command + args + env + cwd | Critical — wrong command = harness won't start |
translate_input() | Convert OrchestratorCommand → raw PTY bytes | Critical — wrong encoding = harness gets garbage |
parse_screen() | Extract HarnessEvents from terminal screen | Best-effort — used by workflow engine |
detect_completion() | Determine if harness is done/waiting/failed | Critical — wrong detection = workflow hangs |
capabilities() | Report what this harness supports | Informational |
sideband() | Optional out-of-band channel | Only for Tier 1 harnesses with API access |
Before writing any code, answer these questions:
npm, cargo, go install?Create crates/hom-adapters/tests/<harness_name>_test.rs.
The snippet below is a template, not copy-paste-ready code. Replace placeholder names like MyAdapter, MyHarness, and helper builders with the real adapter and test helpers for your harness:
#[test]
fn test_build_command_default() {
let adapter = MyAdapter::new();
let config = HarnessConfig::new(HarnessType::MyHarness, PathBuf::from("/tmp"));
let spec = adapter.build_command(&config);
assert_eq!(spec.program, "my-harness-binary");
assert!(spec.args.is_empty());
}
#[test]
fn test_build_command_with_model() {
let adapter = MyAdapter::new();
let config = HarnessConfig::new(HarnessType::MyHarness, PathBuf::from("/tmp"))
.with_model("gpt-4");
let spec = adapter.build_command(&config);
assert!(spec.args.contains(&"--model".to_string()));
assert!(spec.args.contains(&"gpt-4".to_string()));
}
#[test]
fn test_detect_completion_waiting() {
let adapter = MyAdapter::new();
let screen = make_screen_with_last_line("❯ ");
assert!(matches!(adapter.detect_completion(&screen), CompletionStatus::WaitingForInput));
}
#[test]
fn test_translate_prompt() {
let adapter = MyAdapter::new();
let bytes = adapter.translate_input(&OrchestratorCommand::Prompt("hello".into()));
assert_eq!(bytes, b"hello\n");
}
Run tests: cargo test -p hom-adapters. They must fail before you write the implementation.
Create crates/hom-adapters/src/<harness_name>.rs. Follow the pattern of existing adapters (see claude_code.rs as reference).
Add to crates/hom-adapters/src/lib.rs:
pub mod <harness_name>;AdapterRegistry::new()In crates/hom-core/src/types.rs:
HarnessType enumdisplay_name() match armdefault_binary() match armfrom_str_loose() match arm(s)In config/default.toml, add a [harnesses.<name>] section.
cargo check # Must pass
cargo test -p hom-adapters # All new tests green
cargo clippy # No warnings
HarnessConfig.env_vars and config.tomlHarnessAdapter trait for one harness — The trait serves ALL 7 harnesses. Use sideband() for harness-specific featuresdetect_completion tests — This is the #1 source of workflow hangsScreenSnapshot methods, not raw string matchingHarnessType variant added with all match armsHarnessAdapter traitAdapterRegistry::new()config/default.toml entry addedfrom_str_loose() handles common aliasesbuild_command, translate_input, detect_completioncargo check && cargo test -p hom-adapters && cargo clippy all pass