Skip to content

AUDIT-04 — High — Gemini thought_signature cannot be persisted; session history silently dropped #61

Closed

Description

@getdownandcode

Summary

Severity: High
Area: Persistence / Gemini
Affected files: agentkit/memory/redis_store.py:72-79, agentkit/core/agent.py:157-161, agentkit/tools/models.py:70-73
Suggested branch: fix/gemini-thought-signature-persistence
Suggested PR title: fix(memory): persist Gemini thought signatures losslessly

This is finding AUDIT-04 of 18 from a consolidated audit of agentkit/. It is deliberately self-contained so it can be fixed on its own branch with its own PR.

Related findings: Related: AUDIT-05 (same adapter), AUDIT-10 (silent persistence failures).


AUDIT-04 — High — Gemini thought_signature cannot be persisted; session history silently dropped

Files

  • agentkit/memory/redis_store.py:72-79
  • agentkit/core/agent.py:157-161
  • agentkit/tools/models.py:70-73 (field definition)

Current code — agentkit/memory/redis_store.py:72-79

    async def save_messages(self, session_id: str, messages: list[Message]) -> None:
        """Persist conversation messages in Redis with TTL and sliding window."""
        if self.max_messages is not None:
            messages = self.truncate_messages(messages, self.max_messages)

        key = self._key(session_id)
        serialized = json.dumps([m.model_dump(mode="json") for m in messages])
        await self._client.set(key, serialized, ex=self.ttl_s)

Current code — agentkit/core/agent.py:157-161

            async def _finalize_and_return(result: RunResult) -> RunResult:
                if session_id and self.memory is not None and hasattr(self.memory, "save_messages"):
                    _repair_trailing_tool_calls(messages)
                    with contextlib.suppress(Exception):
                        await self.memory.save_messages(session_id, messages)

Why this is a bug

ToolCall.thought_signature is bytes | None (agentkit/tools/models.py:70-73). Pydantic's JSON mode defaults to ser_json_bytes="utf8" — it does not base64-encode. Gemini's signatures are raw binary, so model_dump(mode="json") raises UnicodeDecodeError for any non-UTF-8 signature, and save_messages aborts before writing anything. The agent's contextlib.suppress(Exception) then hides it: the entire conversation for that session is never persisted, and the next turn starts with no history.

Reproduced

thought_signature = b"\xff\xfe\x00\x81"   -> model_dump(mode="json") raises UnicodeDecodeError
thought_signature = b"c2VjcmV0"           -> dumped as the plain string "c2VjcmV0" (not base64)

Pass cases

  • thought_signature round-trips byte-for-byte for arbitrary binary payloads (e.g. b"\xff\xfe\x00\x81", b"\x00", empty bytes) through RedisMemoryStore.save_messages → get_messages.
  • The persisted representation is explicitly encoded (e.g. base64) and explicitly decoded on read — the encoding must not depend on pydantic defaults.
  • A non-UTF-8 signature no longer aborts the save: the whole message list is persisted.
  • A failure to persist the session is logged at ERROR with the session id and does not silently present the run as fully successful.
  • A regression test asserts Agent.run(session_id=…) with a Gemini-style non-UTF-8 signature results in a stored history that can be read back and re-sent to the provider.
  • docs/DECISIONS.md:87 (ADR-006's "transparently base64-encoded" claim) is corrected, and docs/PROGRESS.md:354 / docs/PLAN.md:155 are aligned (see AUDIT-16).


Definition of done (per AGENTS.md)

  • New/updated unit and integration tests covering the pass cases above, all passing (pytest).
  • ruff check ., ruff format --check . and mypy agentkit clean.
  • Docstrings updated where behaviour changed; docs/PROGRESS.md updated (status, PR link, commit count, notes).
  • A new ADR in docs/DECISIONS.md if an architectural decision was made.
  • Branch pushed and PR opened (never commit directly to main).

Baseline at the time of writing: pytest is green on main (all tests pass; tests/real/* skipped unless AGENTKIT_REAL_INFRA=1).

Activity

  1. getdownandcode commented on Oct 20, 2026

    @getdownandcode
    OwnerAuthor

    Audit index (18 separate issues, fix one by one)

    ID Issue Severity
    AUDIT-01 #58 Critical
    AUDIT-02 #59 Critical
    AUDIT-03 #60 High
    AUDIT-04 #61 High
    AUDIT-05 #62 High
    AUDIT-06 #63 High/Medium
    AUDIT-07 #64 Medium-High
    AUDIT-08 #65 Medium-High
    AUDIT-09 #66 Medium-High
    AUDIT-10 #67 Medium-High
    AUDIT-11 #68 Medium
    AUDIT-12 #69 Medium
    AUDIT-13 #70 Medium
    AUDIT-14 #71 Medium
    AUDIT-15 #72 Medium
    AUDIT-16 #73 Medium
    AUDIT-17 #74 Low
    AUDIT-18 #75 Low/Deployment

    Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with Fixes #NN in your PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions