Adapters
Mental Model
An adapter is a sealed unit that converts one source format into the canonical event stream. It knows everything about its format and nothing about anything else. See .agents/sow/specs/adapter-contract.md.
Adding a New Adapter
Read the format authoritatively first. Open the upstream source mirror under
/opt/baddisk/monitoring/repos/ai/<tool>/and read its persistence code. Sample real files (sanitized) from the operator's environment.Write the spec first. Create
.agents/sow/specs/adapter-<name>.md. Required sections: source format, record shape, watch strategy, cursor design, mapping to canonical events, sub-agent linkage, known edge cases, references. The spec is the source of truth; the code follows.Create the package.
internal/adapters/<name>/adapter.go. Implement thecanonical.Adapterinterface. Keep the file under 400 lines; split helpers into sibling files.Commit sanitized fixtures.
testdata/<name>/<scenario>/. Scenarios are listed intesting-strategy.md. Runscripts/sanitize-fixture.shbefore committing.Write tests. Scan, Tail, cursor resume, error handling, idempotency. Use golden files.
Register the factory.
internal/adapters/registry.go:registry["myadapter"] = myadapter.FactoryAdd auto-discovery probe.
internal/ingest/discover.go: add a probe entry that detects the format's presence on disk.Update docs.
README.mdSource Formats table;docs/adding-an-adapter.mdif any new patterns emerged.Open or update a SOW. Use the external reviewer gates from
project-second-opinionson the adapter SOW's gap analysis, implementation plan, and implementation before closing the work.
Modifying an Existing Adapter
- Read the adapter's spec under
.agents/sow/specs/adapter-<name>.md. - Read recent commits to that adapter (
git log -p internal/adapters/<name>/). - Check existing fixtures for coverage of the case you're touching.
- Make the change.
- Update the spec in the same commit as the code.
- Run
go test ./internal/adapters/<name>/...with-race. - If the canonical mapping changed, refresh golden files with
-update-goldenand inspect the diff manually before committing.
Common Pitfalls
- Closing the
outchannel. Don't. The ingester owns it. - Panicking on bad input. Don't. Call
opts.OnErrorand continue. - Re-emitting on every Scan call. Use the cursor; the ingester does dedup but adapter-side dedup is far cheaper.
- Forgetting to
Addnew subdirs. fsnotify in Go is not recursive; you mustAddeach subdirectory. - Reading symlinks outside the configured root. Resolve with
filepath.EvalSymlinksand verify the result is inside the root. - Holding files open across goroutine boundaries. Open, read, close in one goroutine.
Cursor Design Tips
- A cursor must be persistable and resumable. Don't store live channels or file handles.
- Idempotent on replay: scanning twice with the same cursor should produce the same events.
- Survive source rewrites: if the source overwrites a file, the cursor must detect it (mtime + size + content hash where appropriate).
Sub-Agent Linkage
The canonical model expresses sub-agents as SessionStartedEvent with ParentNativeID set. The adapter is responsible for figuring out parent/child relationships from the source format and emitting that field correctly. The ingester does the cross-session resolution; the adapter just provides the native IDs.
Validation Checklist
Before marking adapter work complete:
- Spec under
.agents/sow/specs/adapter-<name>.mdmatches the implementation. - Fixtures cover happy/multi-turn/sub-agents/tools/failures/in-progress/cursor-resume.
- Golden files reviewed manually.
-
go test -race ./internal/adapters/<name>/...passes. -
golangci-lint run ./internal/adapters/<name>/...clean. - Auto-discovery probe added.
- README Source Formats table updated.
- Applicable external reviewer gates from
project-second-opinionsconverged for the adapter SOW or substantial milestone.