Repository navigation
Closed
Labels
bugSomething isn't workingSomething isn't working
Description
Summary
Severity: Medium
Area: Sessions
Affected files: agentkit/api/deps.py:86-92, agentkit/memory/redis_store.py:37-52
Suggested branch: fix/session-history-truncation
Suggested PR title: fix(memory): wire bounded session history with boundary-safe truncation
This is finding AUDIT-11 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-10.
AUDIT-11 — Medium — Session truncation is never wired; when enabled it orphans tool messages
Files
agentkit/api/deps.py:86-92agentkit/memory/redis_store.py:37-52
Current code — agentkit/api/deps.py:86-92
settings: Settings = getattr(request.app.state, "settings", None) or get_current_settings(
request
)
run_store = PostgresMemoryStore(factory)
session_store = RedisMemoryStore(redis_client, ttl_s=settings.SESSION_TTL_S)
return TieredMemoryStore(session_store=session_store, run_store=run_store)Current code — agentkit/memory/redis_store.py:37-52
@staticmethod
def truncate_messages(messages: list[Message], max_messages: int) -> list[Message]:
"""Truncate message history, preserving the leading system prompt and recent messages."""
if max_messages <= 0 or len(messages) <= max_messages:
return messages
if messages and messages[0].role == Role.SYSTEM:
system_msg = messages[0]
non_system = messages[1:]
# The system prompt occupies one of the max_messages slots, so only
# max_messages - 1 conversational turns fit. Taking a full tail would silently
# return max_messages + 1 entries and grow the history without bound.
budget = max_messages - 1
return [system_msg, *non_system[-budget:]] if budget > 0 else [system_msg]
return messages[-max_messages:]Why this is a bug
max_messagesis never supplied (no setting exists for it) → session histories grow without bound in Redis.- The truncation is also incorrect when enabled: a naive tail cut can start the history with a
toolmessage whose assistanttool_callsturn was dropped, producing a history the providers reject (OpenAI: "messages with role 'tool' must be a response to a preceding message with 'tool_calls'").
Reproduced
history: [system, user, assistant(c1), tool(c1), assistant(c2), tool(c2)]
truncate(max_messages=4) -> [system, tool(c1), assistant(c2), tool(c2)] # tool(c1) is orphaned
truncate(max_messages=3) -> [system, assistant(c2), tool(c2)] # ok
Pass cases
- A setting (e.g.
SESSION_MAX_MESSAGES) exists, is documented in.env.example/docs/SPEC.md, and is actually passed toRedisMemoryStore. - Truncation never leaves a
toolmessage without its matching assistanttool_callsturn, and never leaves an assistanttool_callsturn without its results. - The system prompt is preserved and the total returned message count never exceeds
max_messages. - A regression test truncating a history containing tool calls at every possible cut point asserts the result is a provider-valid history.
- A test asserts a long session does not grow the stored history beyond the configured bound.
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 .andmypy agentkitclean. - Docstrings updated where behaviour changed;
docs/PROGRESS.mdupdated (status, PR link, commit count, notes). - A new ADR in
docs/DECISIONS.mdif 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).
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't working
Activity
getdownandcode commentedon Oct 20, 2026
Audit index (18 separate issues, fix one by one)
Each issue is self-contained: current code, reproduction, pass cases, suggested branch and PR title. Reference the relevant one with
Fixes #NNin your PR.