Skip to content
6 changes: 3 additions & 3 deletions docs/cli-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -298,9 +298,9 @@ Usage: polylogue read [OPTIONS] [REF]
Projection:
-v, --view VIEW[,VIEW...] What to render (summary, transcript,
dialogue, messages, raw, hooks, events,
context, context-image, neighbors,
correlation, temporal, chronicle).
[default: summary]
file-edits, agent-policies, context,
context-image, neighbors, correlation,
temporal, chronicle). [default: summary]
--render TEXT Render expression, e.g. layout:context-
image,timestamps:include-
available,format:markdown. Known keys:
Expand Down
52 changes: 28 additions & 24 deletions docs/plans/topology-target.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions polylogue/api/archive.py
Original file line number Diff line number Diff line change
Expand Up @@ -1995,6 +1995,7 @@ def _archive_summary_to_domain(summary: ArchiveSessionSummary) -> SessionSummary
git_branch=summary.git_branch,
git_repository_url=summary.git_repository_url,
provider_project_ref=summary.provider_project_ref,
display_name=summary.display_name,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Hydrate display names on full archive reads

This wires display_name only through the summary conversion. The public Polylogue.get_session() path uses _archive_session_to_session(archive.read_session(...)), but ArchiveSessionEnvelope neither selects nor carries sessions.display_name, so full session reads still produce Session.display_name=None and fall back to the raw ID when no title exists. Add the column to both archive-envelope read variants and pass it through the full-session converter.

AGENTS.md reference: AGENTS.md:L351-L353

Useful? React with 👍 / 👎.

message_count=summary.message_count,
tags_m2m=summary.tags,
)
Expand Down Expand Up @@ -5447,6 +5448,76 @@ async def get_session_events(
for event in events
]

async def get_file_edits(self, session_id: str) -> list[dict[str, object]] | None:
"""Return file-edit tool-call evidence (structuredPatch/originalFile/...) for one session.

polylogue-nua7: the writer materializes ``ParsedFileEdit`` evidence
(Claude Code Edit/Write/MultiEdit tool calls -- structured unified
diffs, pre-edit file content, old/new string pairs) into the
dedicated ``file_edits`` index table on every ingest
(``storage/repository/archive/sessions.py::get_file_edits``), but
before this reader nothing above the storage layer could reach it.
This is the read surface: what a "what did this session change"
report needs instead of re-deriving edits from tool-call prose.

Returns ``None`` when the session does not exist (distinct from an
empty list, meaning the session exists but made no captured edits).
"""
resolved = await self.repository.resolve_id(session_id)
resolved_id = str(resolved) if resolved is not None else session_id
session = await self.repository.get(resolved_id)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid loading full transcripts to check session existence

For a large session, each new evidence reader first calls repository.get, which fetches and hydrates every message, attachment, session event, and tag before issuing the small targeted file_edits or session_agent_policies query. Thus even an empty agent-policy projection can consume memory and latency proportional to the entire transcript, making the new CLI/MCP views impractical for the multi-GiB sessions the archive supports; use a lightweight session-record/summary existence check instead. The same issue is repeated in get_agent_policies.

Useful? React with 👍 / 👎.

if session is None:
return None
edits = await self.repository.get_file_edits(resolved_id)
return [
{
"tool_use_block_id": edit.tool_use_block_id,
"message_id": str(edit.message_id),
"file_path": edit.file_path,
"structured_patch": edit.structured_patch,
"original_file": edit.original_file,
"old_string": edit.old_string,
"new_string": edit.new_string,
"replace_all": edit.replace_all,
"user_modified": edit.user_modified,
"observed_at_ms": edit.observed_at_ms,
}
for edit in edits
]

async def get_agent_policies(self, session_id: str) -> list[dict[str, object]] | None:
"""Return sandbox/approval/network policy facts recorded for one session.

polylogue-nua7: the writer diverts Codex ``agent_policy`` events out
of ``session_events`` into the dedicated ``session_agent_policies``
table (fully re-derivable, zero evidence loss -- see
``archive_tiers/write.py:_SESSION_EVENTS_REDUNDANT_TYPES``), but
before this reader nothing above the storage layer could reach it
back. This is the read surface.

Returns ``None`` when the session does not exist (distinct from an
empty list, meaning the session exists but reported no agent-policy
facts -- expected for non-Codex origins).
"""
resolved = await self.repository.resolve_id(session_id)
resolved_id = str(resolved) if resolved is not None else session_id
session = await self.repository.get(resolved_id)
if session is None:
return None
policies = await self.repository.get_agent_policies(resolved_id)
return [
{
"policy_id": policy.policy_id,
"position": policy.position,
"approval_policy": policy.approval_policy,
"sandbox_policy": policy.sandbox_policy,
"network_policy": policy.network_policy,
"observed_at_ms": policy.observed_at_ms,
"source_message_id": policy.source_message_id,
}
for policy in policies
]

async def query_sessions(
self,
*,
Expand Down
9 changes: 9 additions & 0 deletions polylogue/archive/session/domain_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,13 @@ class SessionSummary(SessionSummaryRuntimeMixin, BaseModel):
git_branch: str | None = None
git_repository_url: str | None = None
provider_project_ref: str | None = None
# Provider-assigned human-readable session name distinct from the
# (possibly inferred) title -- e.g. Claude Code's "slug" wire field
# ("greedy-squishing-hamming"), captured but previously dropped before
# reaching any domain model (polylogue-cgfy: 1,500 sampled occurrences,
# the fix for subagent rows displaying "<uuid>:agent-<suffix>" instead
# of a human name).
display_name: str | None = None
parent_id: SessionId | None = None
branch_type: BranchType | None = None
message_count: int | None = None
Expand Down Expand Up @@ -104,6 +111,8 @@ class Session(SessionRuntimeMixin, BaseModel):
git_branch: str | None = None
git_repository_url: str | None = None
provider_project_ref: str | None = None
# See ``SessionSummary.display_name`` (polylogue-cgfy).
display_name: str | None = None
session_events: tuple[SessionEvent, ...] = ()
parent_id: SessionId | None = None
branch_type: BranchType | None = None
Expand Down
7 changes: 7 additions & 0 deletions polylogue/archive/session/domain_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class SessionRuntimeMixin:
metadata: dict[str, object]
parent_id: SessionId | None
branch_type: BranchType | None
display_name: str | None

if TYPE_CHECKING:

Expand Down Expand Up @@ -73,6 +74,12 @@ def display_title(self) -> str:
return user_title
if self.title:
return self.title
# polylogue-cgfy: provider-assigned display name (e.g. Claude Code's
# slug, "greedy-squishing-hamming") beats the raw id truncation --
# the fix for subagent rows showing "<uuid-prefix>" instead of a
# human-readable name when no title-worthy sidecar evidence exists.
if self.display_name:
return self.display_name
Comment on lines +81 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Honor title provenance before choosing the stored title

For existing sessions whose nonblank title is a synthetic native ID with title_source='unknown' or 'path'—the live-data case this change is intended to fix—display_title returns that value before it can reach the new display_name branch. Consequently, repository-hydrated Session objects and the twin SessionSummary implementation still display the UUID instead of the slug; apply the same title-source-aware distinction already used by _summary_from_row.

AGENTS.md reference: AGENTS.md:L354-L356

Useful? React with 👍 / 👎.

return self.id[:8]

@property
Expand Down
4 changes: 4 additions & 0 deletions polylogue/archive/session/summary_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class SessionSummaryRuntimeMixin:
metadata: dict[str, object]
parent_id: SessionId | None
branch_type: BranchType | None
display_name: str | None

@property
def display_date(self) -> datetime | None:
Expand All @@ -40,6 +41,9 @@ def display_title(self) -> str:
return user_title
if self.title:
return self.title
# polylogue-cgfy: see Session.display_title's twin fallback.
if self.display_name:
return self.display_name
return self.id[:8]

@property
Expand Down
Loading