| name | project-adapters |
| description | Step-by-step workflow for adding, modifying, or debugging a source-format adapter. Use whenever editing an adapter under internal/adapters, internal/canonical, or related fixture files. |
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 the canonical.Adapter interface. Keep the file under 400 lines; split helpers into sibling files.
-
Commit sanitized fixtures. testdata/<name>/<scenario>/. Scenarios are listed in testing-strategy.md. Run scripts/sanitize-fixture.sh before committing.
-
Write tests. Scan, Tail, cursor resume, error handling, idempotency. Use golden files.
-
Register the factory. internal/adapters/registry.go:
registry["myadapter"] = myadapter.Factory
-
Add auto-discovery probe. internal/ingest/discover.go: add a probe entry that detects the format's presence on disk.
-
Update docs. README.md Source Formats table; docs/adding-an-adapter.md if any new patterns emerged.
-
Open or update a SOW. Use the external reviewer gates from project-second-opinions on 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-golden and inspect the diff manually before committing.
Common Pitfalls
- Closing the
out channel. Don't. The ingester owns it.
- Panicking on bad input. Don't. Call
opts.OnError and continue.
- Re-emitting on every Scan call. Use the cursor; the ingester does dedup but adapter-side dedup is far cheaper.
- Forgetting to
Add new subdirs. fsnotify in Go is not recursive; you must Add each subdirectory.
- Reading symlinks outside the configured root. Resolve with
filepath.EvalSymlinks and 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: