Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 13 additions & 10 deletions src/praisonai/praisonai/integrations/managed_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -486,18 +486,21 @@ def _persist_state(self) -> None:
store.set_metadata(self._session_id, state)
return

# DefaultSessionStore path — write into SessionData.metadata
# DefaultSessionStore path — merge metadata under file lock (preserves messages)
try:
session = store.get_session(self._session_id)
if session is not None:
if not isinstance(session.metadata, dict):
session.metadata = {}
session.metadata.update(state)
store._save_session(session)
if hasattr(store, "update_session_metadata"):
store.update_session_metadata(self._session_id, **state)
Comment on lines +491 to +492
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Stale compute_instance_id after instance shutdown

DefaultSessionStore.update_session_metadata silently skips any field whose value is None (see if value is None: continue in store.py:541). This means if a compute instance is freed mid-session — setting self._compute_instance_id = None at line 1043 — the subsequent _persist_state() call won't overwrite the old instance ID that is already in the metadata. On the next _restore_state(), line 547 (if meta.get("compute_instance_id"): self._compute_instance_id = ...) will restore the stale ID, leaving the agent referencing a freed compute resource.

The same applies to compute_provider when self._compute is None. Previously the raw session.metadata.update(state) explicitly stored None, clearing the stale value. The new path no longer does so.

else:
from praisonaiagents.session.store import SessionData
new_session = SessionData(session_id=self._session_id, metadata=state)
store._save_session(new_session)
session = store.get_session(self._session_id)
if session is None:
from praisonaiagents.session.store import SessionData

store._save_session(SessionData(session_id=self._session_id, metadata=state))
else:
if not isinstance(session.metadata, dict):
session.metadata = {}
session.metadata.update(state)
store._save_session(session)
except Exception as e:
logger.debug("[local_managed] _persist_state failed: %s", e)

Expand Down
32 changes: 32 additions & 0 deletions src/praisonai/tests/unit/integrations/test_managed_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,38 @@ def test_backward_compatible_aliases():
assert ManagedBackendConfig == ManagedConfig


def test_local_managed_persist_state_preserves_messages():
"""_persist_state must not overwrite messages added concurrently on disk."""
import tempfile

from praisonaiagents.session.store import DefaultSessionStore
from praisonai.integrations.managed_local import LocalManagedAgent

with tempfile.TemporaryDirectory() as session_dir:
writer = DefaultSessionStore(session_dir=session_dir)
reader = DefaultSessionStore(session_dir=session_dir)

session_id = "managed-race-session"
writer.add_user_message(session_id, "first")
reader._load_session(session_id)
writer.add_user_message(session_id, "second")

agent = LocalManagedAgent(session_store=reader)
agent._session_id = session_id
agent.agent_id = "agent-race"
agent.total_input_tokens = 42
agent._persist_state()

writer.invalidate_cache(session_id)
history = writer.get_chat_history(session_id)
assert len(history) == 2
assert history[1]["content"] == "second"

session = writer.get_session(session_id)
assert session.metadata.get("agent_id") == "agent-race"
assert session.metadata.get("total_input_tokens") == 42


@patch('praisonai.integrations.managed_agents.logger')
def test_logging_integration(mock_logger):
"""Test that managed agents include proper logging."""
Expand Down
Loading