BUG-HUNT: [data-flow] Session.as_export_dict() omits linked_plans (phase/state metadata), causing data loss on round-trip import #6418

Open
opened 2026-04-09 21:02:02 +00:00 by HAL9000 · 0 comments
Owner

Bug Report: [data-flow] — Session.as_export_dict() omits linked_plans (rich LinkedPlan objects with phase+state), causing data loss when sessions are exported and re-imported

Severity Assessment

  • Impact: When a session is exported via as_export_dict() and then imported via import_session(), all linked_plans data (plan phase and state information) is permanently lost. Only the flat linked_plan_ids list survives the round-trip.
  • Likelihood: Medium — affects every user who uses agents session export + agents session import
  • Priority: Medium

Location

  • File: src/cleveragents/domain/models/core/session.py
  • Function/Class: Session.as_export_dict
  • Lines: 425–465

Description

The Session model stores plan linkage in two fields:

  1. linked_plan_ids: list[str] — flat list of ULID strings (for persistence compatibility)
  2. linked_plans: list[LinkedPlan] — richer objects with plan_id, phase, and state

The as_export_dict() method only serializes linked_plan_ids but omits linked_plans. Since import_session() reconstructs the session from the exported dict, the imported session will have linked_plans = [] even if the original had phase/state data populated.

The docstring claims the export includes linked_plan_ids but the spec's CLI output format (agents session show) requires linked_plans with phase and state (as evidenced by as_cli_dict() which correctly uses linked_plans for display).

Evidence

# src/cleveragents/domain/models/core/session.py, lines 447-462

export: dict[str, Any] = {
    "schema_version": EXPORT_SCHEMA_VERSION,
    "session_id": self.session_id,
    "actor_name": self.actor_name,
    "namespace": self.namespace,
    "messages": messages_data,
    "linked_plan_ids": list(self.linked_plan_ids),  # ← Only flat IDs exported
    # BUG: "linked_plans" is NOT included here!
    "token_usage": {
        "input_tokens": self.token_usage.input_tokens,
        "output_tokens": self.token_usage.output_tokens,
        "estimated_cost": self.token_usage.estimated_cost,
    },
    "metadata": dict(self.metadata),
    "created_at": self.created_at.isoformat(),
    "updated_at": self.updated_at.isoformat(),
}

The as_cli_dict() method shows the correct expected behavior — it uses linked_plans:

# Lines 392-399
if self.linked_plans:
    result["linked_plans"] = [
        {"plan_id": lp.plan_id, "phase": lp.phase, "state": lp.state}
        for lp in self.linked_plans
    ]

Round-trip demonstration:

session = Session(...)
session.linked_plans = [LinkedPlan(plan_id="01...", phase="execute", state="complete")]

exported = session.as_export_dict()
# exported["linked_plans"] → KeyError (missing!)
# exported["linked_plan_ids"] → ["01..."]  (flat IDs only)

# After import, linked_plans will be empty:
imported = import_session(exported)
imported.linked_plans  # → []  ← DATA LOSS

Expected Behavior

as_export_dict() should include a linked_plans entry with the full LinkedPlan objects (plan_id, phase, state) so that an imported session has the same phase/state metadata as the original.

Actual Behavior

as_export_dict() only exports linked_plan_ids (flat list). The linked_plans list (with phase/state) is silently dropped, causing data loss on export/import round-trips.

Suggested Fix

Add linked_plans serialization to as_export_dict():

export: dict[str, Any] = {
    ...
    "linked_plan_ids": list(self.linked_plan_ids),
    "linked_plans": [  # ADD THIS
        {"plan_id": lp.plan_id, "phase": lp.phase, "state": lp.state}
        for lp in self.linked_plans
    ],
    ...
}

And update import_session() to reconstruct LinkedPlan objects from the exported list.

Category

data-flow

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [data-flow] — `Session.as_export_dict()` omits `linked_plans` (rich `LinkedPlan` objects with phase+state), causing data loss when sessions are exported and re-imported ### Severity Assessment - **Impact**: When a session is exported via `as_export_dict()` and then imported via `import_session()`, all `linked_plans` data (plan phase and state information) is permanently lost. Only the flat `linked_plan_ids` list survives the round-trip. - **Likelihood**: Medium — affects every user who uses `agents session export` + `agents session import` - **Priority**: Medium ### Location - **File**: `src/cleveragents/domain/models/core/session.py` - **Function/Class**: `Session.as_export_dict` - **Lines**: 425–465 ### Description The `Session` model stores plan linkage in two fields: 1. `linked_plan_ids: list[str]` — flat list of ULID strings (for persistence compatibility) 2. `linked_plans: list[LinkedPlan]` — richer objects with `plan_id`, `phase`, and `state` The `as_export_dict()` method only serializes `linked_plan_ids` but **omits `linked_plans`**. Since `import_session()` reconstructs the session from the exported dict, the imported session will have `linked_plans = []` even if the original had phase/state data populated. The docstring claims the export includes `linked_plan_ids` but the spec's CLI output format (`agents session show`) requires `linked_plans` with phase and state (as evidenced by `as_cli_dict()` which correctly uses `linked_plans` for display). ### Evidence ```python # src/cleveragents/domain/models/core/session.py, lines 447-462 export: dict[str, Any] = { "schema_version": EXPORT_SCHEMA_VERSION, "session_id": self.session_id, "actor_name": self.actor_name, "namespace": self.namespace, "messages": messages_data, "linked_plan_ids": list(self.linked_plan_ids), # ← Only flat IDs exported # BUG: "linked_plans" is NOT included here! "token_usage": { "input_tokens": self.token_usage.input_tokens, "output_tokens": self.token_usage.output_tokens, "estimated_cost": self.token_usage.estimated_cost, }, "metadata": dict(self.metadata), "created_at": self.created_at.isoformat(), "updated_at": self.updated_at.isoformat(), } ``` The `as_cli_dict()` method shows the correct expected behavior — it uses `linked_plans`: ```python # Lines 392-399 if self.linked_plans: result["linked_plans"] = [ {"plan_id": lp.plan_id, "phase": lp.phase, "state": lp.state} for lp in self.linked_plans ] ``` Round-trip demonstration: ```python session = Session(...) session.linked_plans = [LinkedPlan(plan_id="01...", phase="execute", state="complete")] exported = session.as_export_dict() # exported["linked_plans"] → KeyError (missing!) # exported["linked_plan_ids"] → ["01..."] (flat IDs only) # After import, linked_plans will be empty: imported = import_session(exported) imported.linked_plans # → [] ← DATA LOSS ``` ### Expected Behavior `as_export_dict()` should include a `linked_plans` entry with the full `LinkedPlan` objects (plan_id, phase, state) so that an imported session has the same phase/state metadata as the original. ### Actual Behavior `as_export_dict()` only exports `linked_plan_ids` (flat list). The `linked_plans` list (with phase/state) is silently dropped, causing data loss on export/import round-trips. ### Suggested Fix Add `linked_plans` serialization to `as_export_dict()`: ```python export: dict[str, Any] = { ... "linked_plan_ids": list(self.linked_plan_ids), "linked_plans": [ # ADD THIS {"plan_id": lp.plan_id, "phase": lp.phase, "state": lp.state} for lp in self.linked_plans ], ... } ``` And update `import_session()` to reconstruct `LinkedPlan` objects from the exported list. ### Category data-flow ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: `@tdd_issue`, `@tdd_issue_<this-issue-number>`, and `@tdd_expected_fail` to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
HAL9000 added this to the v3.2.0 milestone 2026-04-09 21:09:06 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#6418
No description provided.