21 KiB
Audita Go Rewrite Notes
Rewrite strategy
The Go rewrite should be compatibility-first. The existing Python implementation should be treated as the executable specification for public behavior, pipeline semantics, diagnostics, and safety rules.
The rewrite should not begin as a redesign. The first production-capable Go version should be able to stand in for the Python CLI in the surrounding transcript pipeline.
The initial goal is:
same public contract
same pipeline semantics
same safety posture
same diagnostic philosophy
idiomatic Go implementation
reliable subprocess behavior
Exact LLM output parity is not required because LLM calls are nondeterministic and may vary by backend, prompt formatting, model, or structured-output implementation. Deterministic framework behavior should be held to a much stricter compatibility standard.
Core principles
- Preserve behavior before improving behavior.
- Port deterministic layers before LLM layers.
- Test with fake LLMs before testing with real LLMs.
- Keep module stages sequential until correctness is established.
- Add bounded concurrency only after the sequential implementation is correct.
- Keep the CLI contract stable for downstream callers.
- Keep stdout/stderr behavior clean and predictable.
- Treat diagnostics and reports as part of the product, not as afterthoughts.
- Avoid prompt improvements during the port.
- Prefer explicit Go structs and validation over reflection-heavy abstractions.
Compatibility targets
The Go implementation should preserve the following public behaviors where practical:
audita processcommand shape;- transcript input support for bare segment arrays and
{ "segments": [...] }objects; - glossary YAML support;
- default module sequence;
- repeated module instance naming;
- environment variable and CLI flag configuration concepts;
- CLI-over-env precedence;
- output file behavior;
- stdout behavior when
--outputis omitted; - stderr logging behavior;
- separate
--report-jsonmachine-readable report behavior; - work-dir diagnostics behavior;
- retention modes
auto,always, andnever; - failed-run diagnostics preservation;
- skipped-change reporting instead of crashing on stale or unsafe proposal application.
Current Go rewrite status (Phase 3 foundation)
The Go rewrite now includes deterministic foundation pieces needed before real module/LLM behavior:
- typed transcript parsing and validation (bare array or
{ "segments": [...] }); - typed glossary YAML parsing and validation;
- deterministic transcript normalization and normalization summary;
- deterministic token estimation;
- contiguous transcript chunking with section metadata;
- correction proposal data models and enriched proposal metadata;
- replacement policy handling (
require_unique,replace_all); - safe per-segment proposal preview;
- deterministic transcript proposal application in stable proposal-index order;
- applied and skipped change records with stable skip reasons;
- report/diagnostic-oriented chunking summary and chunk metadata hooks;
- minimal framework contracts/interfaces for future modules, validators, and structured LLM clients;
- test-only fake-module composition tests proving chunking and proposal application can work together.
The Go rewrite does not yet include end-to-end transcript polishing behavior:
- no real module execution pipeline yet;
- no real
glossary,homophones,spoken_word, orgrammarmodule implementations; - no LLM proposal generation;
- no validator-chain execution;
- no concrete structured LLM client implementation;
- no final LLM-driven transcript polishing behavior comparable to Python production runs.
This means the Go binary should not be described as a complete LLM transcript polisher yet.
Developer quick note
Use these commands while developing deterministic rewrite phases:
go test ./...
go run ./cmd/audita process internal/cli/testdata/tiny_transcript.json --glossary internal/cli/testdata/tiny_glossary.yaml --output /tmp/audita-go-output.json --report-json /tmp/audita-go-report.json
The second command is a representative CLI wiring check only; it is not a real LLM polishing run in the current phase.
Phase 0: Freeze the Python implementation as the reference
Before writing substantial Go code, preserve the behavior of the current Python implementation.
Tasks:
- Create or identify a stable branch/tag representing the Python reference implementation.
- Preserve the existing README and architecture notes.
- Preserve the Python regression suite.
- Collect representative transcript/glossary fixtures.
- Capture reference outputs for deterministic behaviors.
- Capture several real end-to-end run artifacts for qualitative comparison.
Representative fixtures should include:
- tiny valid transcript;
- transcript with multiple speakers;
- transcript accepted as a bare array;
- transcript accepted as an object with
segments; - glossary-supported correction case;
- homophone/mistranscription case;
- spoken-word dysfluency case;
- grammar/punctuation/capitalization case;
- duplicate
original_textcase; - missing
original_textcase; - skipped-change case;
- malformed input case;
- failed LLM response case.
Definition of done:
- A developer or LLM agent can run the Python test suite.
- Reference fixtures are committed.
- Reference reports or golden artifacts exist for deterministic comparisons.
- The Python implementation can be used to answer disputed behavior questions during the rewrite.
Phase 1: Create the Go CLI skeleton
Build the smallest Go binary that preserves the outer command shape.
Tasks:
- Initialize Go module.
- Add
cmd/audita/main.go. - Implement
audita processcommand. - Parse core flags:
- transcript path;
--glossary;--output;--report-json;--modules;- LLM config flags;
- normalization flags;
- work-dir flags.
- Implement config defaults.
- Implement environment variable loading.
- Implement CLI-over-env precedence.
- Implement redaction for sensitive config values.
- Load input files but initially write transcript back unchanged.
Definition of done:
go test ./...passes.go run ./cmd/audita process transcript.json --glossary glossary.yaml --output corrected.jsonsucceeds for a minimal fixture.- The output transcript is valid JSON.
- With
--output, stdout is empty except for intentional machine output, preferably empty. - Logs go to stderr.
- Invalid flags produce a clean error and nonzero exit.
Phase 2: Port schemas and file I/O
Implement typed transcript, glossary, and report data structures.
Tasks:
- Define transcript segment structs.
- Support input as bare segment array.
- Support input as object with
segmentsarray. - Preserve optional
categories. - Validate required fields.
- Validate basic timing shape.
- Define glossary structs matching the existing YAML format.
- Implement glossary parsing.
- Implement report structs at least sufficient for early phases.
- Implement JSON/YAML read/write helpers.
Definition of done:
- Valid Python-era fixtures parse successfully.
- Invalid fixtures fail with clear errors.
- Transcript round-trips through Go without accidental data loss.
- Glossary fixtures parse successfully.
- Report JSON can be written and parsed by tests.
Phase 3: Port deterministic normalization
Port normalization before any LLM work.
Tasks:
- Implement same-speaker segment merging.
- Implement maximum merge gap.
- Implement ellipsis gap insertion.
- Implement maximum merged segment duration.
- Implement maximum merged segment token budget.
- Reassign strict sequential segment IDs starting at
1. - Produce a normalization summary.
- Preserve source transcript and normalized transcript diagnostics.
Definition of done:
- Normalization tests pass against golden fixtures.
- Segment IDs are deterministic.
- Chronological ordering is deterministic.
- Normalization summary is included in the run report.
- Normalization artifacts are written in the run directory.
Phase 4: Port chunking and token estimation
Implement the section-building logic used by proposal generation and validator batching.
Tasks:
- Implement approximate token estimator.
- Implement contiguous transcript sections.
- Implement minimum and maximum section token settings.
- Implement exact target section count behavior if supported by the Python implementation.
- Implement validation prompt batching by token limit.
- Add tests for edge cases.
Edge cases:
- very small transcript;
- single very large segment;
- many short segments;
- exact target section count impossible;
- min/max token bounds conflict;
- speaker boundaries near section boundaries.
Definition of done:
- Chunking is deterministic.
- Sections preserve transcript order.
- Sections do not drop or duplicate segments.
- Token-boundary behavior is covered by tests.
Phase 5: Port proposal model and application semantics
Implement correction proposals and safe replacement behavior.
Tasks:
- Define correction proposal struct.
- Define enriched proposal metadata.
- Define replacement policies:
require_unique;replace_all.
- Implement proposal preview.
- Implement proposal application.
- Implement skipped-change reporting.
- Handle stale proposals safely.
- Add tests for every skip reason.
Skip cases should include:
- missing target segment;
- missing
original_text; - multiple matches under
require_unique; - empty corrected text;
- identical original/corrected text after normalization;
- proposal invalidated by earlier edits.
Definition of done:
- Proposal application never panics on malformed proposal input.
- Unsafe proposals are skipped and reported.
- Applied changes and skipped changes are serializable in the report.
- Tests cover both replacement policies.
Phase 6: Implement reports and diagnostics
Build diagnostics and reporting before real LLM calls so failures are inspectable from the beginning.
Tasks:
- Create per-run directory under configured work dir.
- Write redacted invocation/config metadata.
- Write source transcript artifact.
- Write normalized transcript artifact.
- Write normalization summary artifact.
- Write module prompt/response artifacts once module execution exists.
- Write authoritative
report.jsoninto retained run directories. - Implement
--report-jsonoutput path. - Implement
error.logon failure. - Implement retention modes.
Retention behavior:
always keep all run directories
never remove successful run directories
auto keep failed runs and successful runs with skipped corrections
Failed runs are always retained.
Definition of done:
- Clean successful runs obey retention policy.
- Successful runs with skipped corrections are retained under
auto. - Failed runs are always retained.
- Failed runs write
report.jsonanderror.logwhen possible. - CLI stderr points to retained diagnostics on failure.
Phase 7: Implement the pipeline runner with fake modules
Build the orchestration engine before porting real correction modules.
Tasks:
- Define module interface.
- Define validator interface.
- Define runner context.
- Define module run report model.
- Implement module sequence resolution.
- Implement repeated module instance naming.
- Implement sequential module execution.
- Implement fake module for tests.
- Implement fake validator for tests.
- Wire proposal application into runner.
Definition of done:
- A fake module can propose a correction and the runner applies it.
- A fake validator can reject a correction and the runner reports it.
- Multiple modules run in configured order.
- Repeated module keys are named deterministically.
- A module failure produces a partial failed report.
Phase 8: Port deterministic validators
Port cheap, deterministic validation before LLM-backed validation.
Tasks:
- Implement confidence threshold validator.
- Implement original-text presence validator.
- Implement non-empty correction validator.
- Implement identical-text rejection.
- Implement protected vocabulary logic from glossary.
- Enforce validator result cardinality.
- Add tests for malformed validator results.
Definition of done:
- Deterministic validators run before LLM validators.
- Each validator returns exactly one decision per input proposal.
- Missing, duplicate, or unknown proposal indexes produce framework errors.
- Rejected proposals are reported with reasons.
Phase 9: Implement structured LLM client
Add the OpenAI-compatible structured-output client after the deterministic framework is stable.
Tasks:
- Define
StructuredLLMClientinterface. - Define structured request type.
- Implement OpenAI-compatible chat completions client.
- Support configurable base URL, model, API key, timeout, and retries.
- Support optional API key for self-hosted endpoints.
- Implement structured JSON response parsing.
- Implement response validation.
- Implement retry behavior for malformed structured output.
- Write raw prompt/response diagnostics.
- Support separate effective validation LLM settings.
Definition of done:
- Fake LLM tests still pass.
- Real LLM smoke test can run against a configured endpoint.
- Malformed LLM responses are retried or reported cleanly.
- API keys are never logged or written to diagnostics.
- Context timeout/cancellation works.
Phase 10: Add bounded concurrency
Add concurrency only after sequential correctness is established.
Tasks:
- Add LLM call scheduler/semaphore.
- Add proposal-generation concurrency across transcript sections.
- Add validator batching concurrency where useful.
- Ensure all goroutines are attached to run context.
- Use
errgroupor equivalent controlled error propagation. - Add tests for concurrency limits using fake LLM instrumentation.
Rules:
- Module stages remain sequential.
- LLM backend calls must respect configured concurrency.
- No unbounded goroutine creation.
- Cancellation must stop pending work promptly.
Definition of done:
- Concurrency limit is enforced in tests.
- Results remain deterministic where ordering matters.
- Module reports remain stable.
- Cancellation and timeout behavior are covered.
Phase 11: Port real modules one at a time
Port modules after the framework, validators, and LLM client are in place.
Recommended order:
grammarglossaryhomophonesspoken_word- full default sequence
This order exercises formatting first, then domain-specific correction, then more subtle semantic cleanup.
For each module:
- Port prompt builder without improving wording.
- Define structured response type.
- Define JSON schema if supported by the backend.
- Convert structured response into framework proposals.
- Attach intended validators.
- Add fake-LLM tests.
- Add fixture-level integration tests.
- Add real LLM smoke test where practical.
- Compare qualitative output against Python reference.
Definition of done for each module:
- Fake-LLM tests pass.
- Module-specific fixtures pass.
- Prompt/response diagnostics are written.
- Validator chain is explicit and tested.
- Module report includes applied and skipped changes.
Phase 12: Full-pipeline compatibility testing
Run complete Go pipeline tests after all modules exist.
Tasks:
- Run full default module sequence with fake LLM responses.
- Run full default module sequence against small real transcript.
- Run against at least one real D&D session transcript.
- Compare output shape against Python reference.
- Compare report shape against Python reference.
- Inspect skipped changes manually.
- Inspect diagnostics manually.
- Verify downstream parser compatibility.
Definition of done:
- Go output can be consumed by the next pipeline stage.
- Report JSON can be consumed by existing tooling or by documented replacement tooling.
- Real run diagnostics are sufficient for debugging.
- Output quality is at least acceptable compared with Python.
Phase 13: Subprocess integration testing
Test the compiled Go binary exactly as surrounding Go applications will call it.
Tasks:
- Create integration tests that execute the binary as a subprocess.
- Test
--outputpath behavior. - Test stdout-only transcript output when
--outputis omitted. - Test stderr-only logs/errors.
- Test
--report-jsonpath behavior. - Test nonzero exit on invalid input.
- Test nonzero exit on LLM failure.
- Test large transcript pipe behavior.
- Test timeout/cancellation behavior from parent process.
Definition of done:
- No stdout/stderr deadlocks.
- Parent Go process can reliably distinguish success from failure by exit code.
- Parent Go process can parse output transcript and report files.
- Failed runs provide diagnostics paths.
Phase 14: Run Python and Go side by side
For a transition period, keep both implementations available.
Tasks:
- Give the Python binary a distinct name if needed, such as
audita-python. - Give the Go binary the canonical
auditaname only after it is ready. - Allow the orchestrator to select implementation during transition.
- Run the same real sessions through both implementations.
- Compare reports and final transcripts.
- Record known intentional differences.
- Fix unintentional compatibility breaks.
Comparison criteria:
- both complete successfully;
- both preserve transcript JSON shape;
- both run the intended module sequence;
- both report applied/skipped changes;
- Go has cleaner subprocess behavior;
- Go diagnostics are at least as useful as Python diagnostics;
- Go transcript quality is acceptable for downstream use.
Definition of done:
- Go implementation has passed several real-session runs.
- Downstream applications can call Go Audita reliably.
- Any report/schema differences are documented.
- Python can be retired from the main path.
Phase 15: Retire or archive the Python implementation
After the Go version becomes the default, preserve the Python implementation as historical reference unless there is a reason to remove it.
Tasks:
- Mark Python implementation as archived or prototype.
- Preserve useful fixtures and tests.
- Preserve prompts and reference outputs.
- Remove Python from production orchestration.
- Update README to describe Go as the active implementation.
- Update installation instructions.
- Update operational docs.
Definition of done:
- Repository clearly identifies the Go implementation as active.
- Python code no longer participates in normal operation.
- Historical Python behavior remains available for reference if needed.
Suggested milestone commits
A reasonable commit sequence for agent-assisted implementation:
- Scaffold Go CLI and config loading.
- Add transcript/glossary schemas and I/O.
- Add normalization and normalization tests.
- Add chunking and token estimation.
- Add proposal model and replacement policies.
- Add reports and diagnostics lifecycle.
- Add pipeline runner with fake modules.
- Add deterministic validators.
- Add structured LLM client interface and fake client.
- Add OpenAI-compatible LLM client.
- Add bounded LLM scheduler.
- Port grammar module.
- Port glossary module.
- Port homophones module.
- Port spoken-word module.
- Enable default full pipeline.
- Add subprocess integration tests.
- Update README and operational documentation.
Each milestone should leave the repository in a passing state.
Guidance for LLM implementation agents
When using an LLM agent such as Codex to implement this rewrite, prefer small prompts tied to a single milestone.
Good agent instructions:
- specify the exact phase being implemented;
- name the files or packages to create;
- require tests for the new behavior;
- require
go test ./...to pass; - prohibit prompt wording changes unless the phase is explicitly about prompts;
- require preserving CLI compatibility;
- require preserving stdout/stderr discipline;
- require staging/committing only after tests pass, if the workflow permits commits.
Avoid broad prompts such as “rewrite Audita in Go.” They invite unnecessary redesign and make review difficult.
Review checklist for each phase
Before accepting a phase implementation, check:
- Does it preserve the public contract?
- Does it introduce unnecessary redesign?
- Are errors typed and understandable?
- Are reports still machine-readable?
- Are diagnostics sufficient to debug failures?
- Are API keys redacted?
- Are stdout and stderr clean?
- Are tests deterministic?
- Does
go test ./...pass? - Is the code idiomatic Go rather than Python-shaped Go?
Historical checkpoint note
Earlier "Phase 2 status" notes are intentionally superseded by
Current Go rewrite status (Phase 3 foundation) near the top of this document.
Use that section as the authoritative current-state snapshot.
When to consider an HTTP API
An HTTP API should be deferred until the Go CLI is stable.
Consider HTTP later only if one or more of these become true:
- multiple callers need a shared long-running Audita worker;
- process startup time becomes a meaningful bottleneck;
- a job queue is needed;
- remote execution becomes necessary;
- centralized concurrency control is preferable to per-process control;
- operational monitoring of Audita as a service becomes valuable.
Even then, the HTTP API should wrap the same core Go pipeline used by the CLI. The CLI should remain the simplest and most reliable integration surface.