Skip to content

fix: compute conversation iteration count live from events instead of a stale stored column#325

Merged
pikann merged 2 commits into
masterfrom
feature/compute-conversation-iteration-count
Jul 24, 2026
Merged

fix: compute conversation iteration count live from events instead of a stale stored column#325
pikann merged 2 commits into
masterfrom
feature/compute-conversation-iteration-count

Conversation

@pikann

@pikann pikann commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #314 — the "N iterations" pill on conversation rows always showed 0.

Root cause: agent_conversations.iteration_count was a stored counter that no runtime path ever incremented. The only writer of the full row, AgentRepository.UpdateConversation, is called exclusively from tests — the real runtime paths (executor, ACP dispatch, bridge, realtime) all go through UpdateConversationStatus, which never touches iteration_count. So the column stayed at its default 0 forever, regardless of how many steps a conversation actually took.

Fix: instead of wiring up a new writer in the ai-agent service (which risks drifting out of sync again, and needs a backfill for historical conversations), the count is now computed live in the API layer from data that's already authoritative: agent_conversation_events. One persisted ActionEvent corresponds to one agent reasoning step/action, so iteration_count is a correlated COUNT(*) subquery over that table, scoped to event_type = 'ActionEvent'.

Changes

  • services/api/migrations/000026_drop_conversation_iteration_count.sql — drops the now-unused stored column.
  • services/api/internal/repository/postgres/agent_repository.goconversationCols (used by ListConversations, FindConversationByID, FindLatestConversationByChatSession) computes iteration_count via a correlated subquery instead of reading the stored column; CreateConversation/UpdateConversation no longer write to it.
  • services/api/test/e2e/conversation_pagination_test.go — adds TestE2EConversationIterationCount, seeding a mix of event types and asserting both the list and single-conversation endpoints return the correct ActionEvent count.

Why compute-on-read instead of increment-on-write

A stored counter needs a writer, and that writer needs to run on every event-persistence path (in-process SDK executor and the ACP bridge daemon) to stay correct — which is exactly the kind of drift that caused this bug in the first place. Computing it from agent_conversation_events at read time means there's a single source of truth, no new write path to keep in sync, and no backfill migration needed for conversations that already ran.

Test plan

  • go build ./... passes
  • go test ./... passes (all Go unit tests)
  • PACA_E2E=1 go test ./test/e2e/... passes against real Postgres/Redis/MinIO via testcontainers, including the new TestE2EConversationIterationCount (seeds SystemPromptEvent/ActionEvent/ObservationEvent rows and confirms iteration_count reflects only the ActionEvent count, on both the list and get-by-id endpoints)
  • python -m pytest passes in services/ai-agent (untouched — no changes needed there)

@pikann pikann changed the title feat: compute conversation iteration count live from events, removing… fix: compute conversation iteration count live from events instead of a stale stored column Jul 24, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed changes — computes iteration_count live from agent_conversation_events instead of trusting a stored column that was never incremented by any runtime path, fixing #314.

  • Replace stored iteration_count with live subquery in conversationCols — counts ActionEvent rows per conversation via the existing (conversation_id, event_type) index
  • Remove iteration_count from INSERT, UPDATE, and record mapping in agent_repository.go
  • Drop the column via migration 000026 using idempotent DROP COLUMN IF EXISTS
  • Add e2e test seeding 3 ActionEvents among 5 total events and asserting iteration_count=3 on both list and get endpoints

Stale reference: docs/architecture/database-schema.md and docs/ai-agent/database-schema.md still describe iteration_count as a stored column — worth updating in a follow-up, but not blocking.

✅ No new issues found.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed changes — incremental delta since the prior review at 3e6fda5: fixes a COUNT(*) vs int type mismatch in the scan struct that would fail on the Postgres bigint return, and extends e2e coverage to the remaining conversationCols call sites plus the zero-events edge case.

  • Fix IterationCount type from int to int64 in agentConversationRecordCOUNT(*) returns Postgres bigint, and the prior int type would cause a scan failure.
  • Add FindLatestConversationByChatSession subtest — exercises the third conversationCols call site, which uses WHERE chat_session_id = ... rather than WHERE id = ....
  • Add zero-ActionEvents subtest — confirms iteration_count is 0 when a conversation has only non-ActionEvent rows.

✅ No new issues found.

Pullfrog  | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) (Kimi K2 not used — the program covers this model; add its provider key to run your pick) | 𝕏

@pikann
pikann merged commit add8fe3 into master Jul 24, 2026
4 checks passed
@pikann
pikann deleted the feature/compute-conversation-iteration-count branch July 24, 2026 09:42
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.

[Bug] “N iterations” pill always shows 0

1 participant