Skip to content

fix: resolve 18 correctness, durability, and honesty gaps - #47

Merged
urmzd merged 4 commits into
mainfrom
fix/hardening-sweep
Jul 11, 2026
Merged

fix: resolve 18 correctness, durability, and honesty gaps#47
urmzd merged 4 commits into
mainfrom
fix/hardening-sweep

Conversation

@urmzd

@urmzd urmzd commented Jul 11, 2026

Copy link
Copy Markdown
Owner

Summary

Single hardening sweep resolving 18 audited issues across the agent core, durability layer, providers, CLI, RAG, knowledge graph, and eval packages — plus DB-backed/E2E integration suites and fixes for all 10 findings from a high-effort adversarial review of the diff.

Agent core

  • Wait() missed run failures: EventStream.Wait() now returns the run's terminal error. ErrorDelta and the stream close error are emitted from a single point and always agree; RunDurable consults the authoritative close error, and a cancelled run can never masquerade as success.
  • Sub-agent failures looked successful: child error deltas / stream errors mark the parent tool result IsError, and child agents inherit the parent's StepRunner with sub-<toolCallID>- namespaced step names.
  • Fallback ignored mid-stream errors: fallback.Provider relays the stream and retries the next provider when an error arrives before any content-bearing delta (usage deltas don't latch the gate, so Anthropic's message_start usage can't disable fallback); abandoned streams are always drained so adapter goroutines can't leak.
  • ConfigContent.Model was dropped: new types.ModelSwitcher (WithModel) on all four adapters and the fallback/retry wrappers; unswitchable providers log a warning instead of silently ignoring the model.

Durability

  • Every tree mutation WALs (archive/restore/checkpoint/rewind/branch/set-active/compaction/root creation), recursive mutations in one transaction.
  • Production WAL + recovery: agent/store/filewal (append-only JSONL, fsync on commit, torn-tail repair, partial-write rollback, atomic Compact) and walrecover.RecoverWAL; RecoverAndLoadTree heals the store at startup, re-attaches the WAL, and compacts the log so growth is bounded per session.
  • Checkpoint round-trip: types.Store.ListCheckpoints (pgstore + memstore), LoadTreeFromStore restores checkpoints, and the new Agent.Checkpoint persists them without requiring WAL recovery. ListCheckpoints is breaking for external Store implementers (v0.x).
  • DBOS: docs now describe the real sequential durable execution; RunInput/RunOutput/StepResult registered with gob (they travel as interface values — unregistered types failed at runtime on real DBOS).

Providers & CLI

  • Generate(ctx, prompt) on anthropic/openai/google adapters via shared types.GenerateText (was ollama-only).
  • MarkedTool implements RichTool — approval-wrapped rich results keep their blocks.
  • PDFs map to native document/file parts in the anthropic and openai adapters (google already native), making every ContentSupport claim truthful.
  • New --embed-provider / SAIGE_EMBED_PROVIDER decouples CLI embeddings from the LLM provider.

RAG

  • Transactional document writes (sections/variants were silently dropped by pgstore before — now actually persisted); ReplaceDocument keeps the prior doc on any mid-ingest failure; KG-stage failure surfaces as ErrPartialIngest with the doc committed; Delete removes the document before its graph facts so a failed delete can't strand a live doc without provenance.
  • Metadata filters compile into SQL — the limit×3 over-fetch is gone.
  • Search fails only when all retrievers fail (ErrPartialSearch + surviving hits otherwise); RRF k configurable via WithFusionK.
  • WithGraph registers the graph retriever, and document delete/replace removes derived graph episodes via the new knowledge DeleteEpisodes(groupID).

Knowledge graph

  • GroupID is real tenant isolation: group_id on entities/relations with group-scoped uniqueness, dedup, and search. Migration backfills legacy rows from episode mentions where unambiguous; entities the old cross-tenant merge bug already fused stay in the default group deliberately (assigning either tenant would leak data).
  • Search errors propagate (ErrPartialSearch for partial fan-out failure); episode metadata persists and rounds back through provenance.

Eval

  • Scorer/metric errors record per case, are excluded from aggregates (no silent zeros), and never abort the suite; an all-error suite returns an error alongside inspectable results.

Migrations & compatibility notes

  • agent_branch/agent_checkpoint are conversation-scoped; the migration backfills conversation_id from each row's root node (recursive CTE). Orphans stay reachable via the legacy "" namespace.
  • All migration backfills are idempotent and verified by upgrade tests that recreate the exact historical schemas from git history.
  • Known follow-up (not in this PR): thread ctx through Tree.Archive/Restore/Checkpoint/Rewind/SetActive.

Evaluation performed

  • High-effort adversarial code review (multi-agent, 28 agents, every finding independently verified): 10 confirmed defects found — all fixed in the final commit (migration data-stranding ×2, delete ordering, fallback usage-delta gate + stream leak, durable-run cancellation, model-switch through wrappers, checkpoint persistence, filewal partial-write corruption + unbounded growth).
  • Live E2E suite (Ollama qwen3.5:4b + Postgres/pgvector + DBOS): 16/16 pass — real tool calling, handoffs, structured output, durable runs with idempotent replay, WAL crash recovery, RAG+KG round trip with graph cleanup.
  • Benchmarks vs main: agent loop and tree within noise, identical allocations (WAL/durability strictly opt-in).

Test plan

  • go build ./..., go vet ./... clean; gofmt clean on all changed files
  • go test ./... green (49 packages); go test -race ./agent/... green
  • golangci-lint run --new-from-rev=main — 0 issues; CI lint/vuln failures fixed (Go 1.25.12, goldmark 1.7.17, gocyclo refactors)
  • DB-backed suites against live Postgres+pgvector: agent/pgstore, rag/pgstore, knowledge/pgstore (incl. schema-upgrade + backfill idempotency, run twice)
  • Live integration suite: Ollama + Postgres + DBOS, 16/16
  • CI green on this PR

urmzd added 4 commits July 11, 2026 02:02
Agent core:
- EventStream.Wait() now returns the run's terminal error; ErrorDelta and
  the stream close error are emitted from a single point and always agree
- sub-agent failures mark the parent tool result as errored, and child
  agents inherit the parent's StepRunner (namespaced steps) for durability
- provider fallback now also triggers on mid-stream errors that arrive
  before any forwarded content; later errors propagate without duplication
- ConfigContent.Model is honored via the new types.ModelSwitcher seam,
  implemented by all four provider adapters

Durability:
- every tree mutation (archive/restore/checkpoint/rewind/branch/compact/
  set-active) writes WAL ops in a single transaction
- new agent/store/filewal: production append-only JSONL WAL with fsync on
  commit and torn-tail repair; agent/store/walrecover heals a store from
  committed-unapplied transactions; RecoverAndLoadTree wires it at startup
- checkpoints round-trip: Store.ListCheckpoints (pgstore + memstore) feeds
  LoadTreeFromStore so rewind works after restore
- DBOS adapter docs now describe the real sequential durable execution

Providers & CLI:
- Generate(ctx, prompt) implemented on anthropic/openai/google adapters
  via shared types.GenerateText (previously ollama-only seam)
- MarkedTool implements RichTool: approval-wrapped rich results keep blocks
- PDFs map to native document/file parts in anthropic and openai adapters,
  so ContentSupport claims are now truthful for every provider
- new --embed-provider / SAIGE_EMBED_PROVIDER decouples the CLI embedding
  provider from the LLM provider

RAG:
- document writes are transactional (pgstore tx; sections/variants now
  actually persisted); replace keeps the prior doc on mid-ingest failure
- metadata filters push down into SQL, removing the limit*3 over-fetch
- partial retriever failure returns surviving hits + ErrPartialSearch;
  RRF k is configurable via WithFusionK
- WithGraph registers the graph retriever and deletes graph episodes on
  document delete/replace (knowledge gains DeleteEpisodes end to end)

Knowledge graph:
- GroupID is real tenant isolation: group-scoped entity uniqueness, dedup,
  relations, and search (schema migration included; empty group = legacy)
- search errors propagate (ErrPartialSearch for partial fan-out failures)
  and episode metadata persists to a JSONB column and rounds back

Eval:
- scorer/metric errors record per-case and are excluded from aggregates
  instead of aborting the suite or silently scoring zero
- bump Go to 1.25.12 (GO-2026-5856 crypto/tls) and goldmark to v1.7.17
  (GO-2026-5320 XSS)
- extract embedVariants/writeDocument/enrichGraph from pipeline Ingest and
  retrieveAll/fuseHits from Search to bring cyclomatic complexity under the
  gocyclo threshold; no behavior change
- rag/pgstore: transactional document-tree persistence, atomic replace with
  mid-transaction rollback, metadata filter pushdown past the old limit*3
  window, JSONB filter semantics, vector ordering (7 tests)
- knowledge/pgstore: group isolation for entities/relations/search, episode
  metadata round-trip, DeleteEpisodes cascade, in-place migration upgrade
  from the historical unscoped schema (5 tests)
- integration/: opt-in E2E suite (Ollama/Postgres/DBOS-gated, hermetic
  skips) covering the full agent loop, handoffs, structured output, RAG+KG
  round trip with graph cleanup on delete, WAL crash recovery via
  RecoverAndLoadTree, DBOS durable runs and idempotent replay; includes
  docker-compose infra and Justfile recipes
- dbos: register RunInput/RunOutput/StepResult with gob — the serializer
  encodes them as interface values, so unregistered types fail at runtime
- tree.FromStore/LoadTreeFromStore accept tree.Option and RecoverAndLoadTree
  re-attaches the WAL, so recovered sessions keep write-ahead protection
- FallbackError.Error() now includes the underlying provider errors
Migrations (data stranding):
- backfill kg_entity/kg_relation group_id from episode mentions where the
  group is unambiguous; cross-tenant-merged entities deliberately stay in
  the default group rather than leak between tenants
- backfill agent_branch/agent_checkpoint conversation_id from each row's
  root node via recursive CTE; orphans stay in the legacy namespace

Agent core:
- fallback relay: UsageDelta (emitted at message_start before content) no
  longer latches the no-fallback gate, and every relay return path drains
  an abandoned source stream so provider goroutines can't leak
- RunDurable uses stream.Wait() (authoritative close error) instead of a
  racy in-band ErrorDelta, and an empty response under a dead context is
  reported as ErrStreamCanceled, so cancelled runs can't masquerade as
  success
- ConfigContent.Model now propagates through fallback and retry wrappers
  (both implement ModelSwitcher); an unswitchable provider logs a warning
  instead of silently ignoring the requested model
- new Agent.Checkpoint persists checkpoints through the Store so they
  round-trip without WAL recovery; Tree.Checkpoint docs state the contract

Durability:
- filewal rolls back partial writes by truncating to the pre-write offset
  (self-disabling if rollback fails) so mid-log corruption is impossible
- filewal.Compact atomically rewrites the log keeping only unapplied
  transactions; RecoverWAL compacts after a successful pass, bounding
  growth to one session

RAG:
- Pipeline.Delete removes the store document before graph episodes, so a
  failed delete can no longer destroy graph facts for a live document
@urmzd
urmzd merged commit 63cc20b into main Jul 11, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant