Skip to content

feat(omp): browse oh-my-pi sub-agent transcripts, folded under their parent#27

Merged
rollysys merged 3 commits into
mainfrom
feat/omp-subagent-transcripts
Jun 8, 2026
Merged

feat(omp): browse oh-my-pi sub-agent transcripts, folded under their parent#27
rollysys merged 3 commits into
mainfrom
feat/omp-subagent-transcripts

Conversation

@rollysys

@rollysys rollysys commented Jun 8, 2026

Copy link
Copy Markdown
Owner

Problem

oh-my-pi (omp) writes each task/job sub-agent's full conversation to its own jsonl in a sibling directory named after the parent file's stem — a single parent can spawn hundreds (one real session here has 918). The omp provider only scanned top-level *.jsonl files and never entered that directory, so the parent's task result was just [Output truncated - N tokens] and the actual sub-agent content was completely unreadable.

What this does

Sub-agents are now browsable, shown as a second indentation tier folded under the session that spawned them (collapsed by default), reusing the existing group-fold machinery.

Data layer (core)

  • SessionMeta gains parent_id: Option<String> and child_count: usize.
  • omp::summarize counts a parent's children at discovery via a directory listing only (no file reads), so startup cost is unchanged.
  • omp::list_children parses the children lazily, only when a parent is expanded — the hundreds of child files are never read at startup.
  • Sub-agents get a unique cache-safe id (omp:sub:<parent>:<stem>), inherit the parent's cwd, and use the filename slug as a readable label.
  • Top-level discovery still returns only parents, so session grouping is unchanged.

TUI (tui)

  • New ListRow::SubAgent tier, an expanded_parents set, and a lazily-populated children map.
  • A parent that spawned sub-agents shows a ▶N⤷ / ▼N⤷ fold marker; Space toggles it.
  • meta_by_sid resolves a selected sub-agent to its meta, so preview/detail render its transcript through the normal read_transcript path — no downstream special-casing.
  • Auto-refresh clears the sub-agent cache + fold state.
  • --dry-run gains an omp sub-agent diagnostic (host count, total children, live list_children probe).

Verification

  • cargo test (workspace) — green, incl. a new child_sub_agents_are_discovered_and_linked test.
  • Two-host build: macOS (debug+release) and Linux/dev (release, in-place) both compile clean.
  • Real-data smoke on dev (~/.omp, 816 sessions): top level indexes parents only; omp sub-agent hosts: 5 (total children=1016); probing the largest parent → child_count=918 loaded=918 with correct slug-based ids, no crash, startup still fast.

Interactive TUI fold/expand was validated on dev by building locally there (dev runs Ubuntu 20.04 / glibc 2.31, which the GitHub linux-gnu release tarball is too new for — a separate musl-target follow-up would let the prebuilt binary run there too).

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Summary by CodeRabbit

Release Notes

  • New Features

    • Sessions now support hierarchical relationships, enabling display of child sub-agent sessions in the TUI with independent expand/collapse controls.
    • Session relationship diagnostics added to dry-run output, showing parent-child session summaries.
  • Tests

    • Extended coverage for hierarchical session discovery and metadata linkage.

…yer)

oh-my-pi writes each `task`/`job` sub-agent's full conversation to its own
jsonl in a sibling directory named after the parent file's stem (a single
parent can spawn hundreds). The omp provider only scanned top-level `*.jsonl`
files, so sub-agent content was completely invisible.

- SessionMeta gains `parent_id: Option<String>` (sub-agent → parent linkage)
  and `child_count: usize` (cheap directory-listing count, no file reads).
- omp::summarize counts a parent's children at discovery; omp::list_children
  lazily parses them on demand (so startup never reads the hundreds of child
  files — they load only when the user expands a parent).
- Sub-agent SessionMetas get a unique cache-safe id (`omp:sub:<parent>:<stem>`),
  inherit the parent's cwd, and use the filename slug as a readable label.
- All other providers set the two new fields to None / 0.

Top-level discovery still returns only parents, so grouping is unchanged. TUI
fold/expand rendering comes next.

Co-Authored-By: Claude <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rollysys, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 1 second. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0cbae7ec-cfd1-420a-8b74-6e988b1480c9

📥 Commits

Reviewing files that changed from the base of the PR and between c525951 and 89e8180.

📒 Files selected for processing (2)
  • tui/src/main.rs
  • tui/src/tui.rs
📝 Walkthrough

Walkthrough

SessionMeta gains parent/child relationship tracking. All providers initialize the new fields. OMP adds child-transcript discovery via a new public function. The TUI extends list rendering to show lazy-loaded sub-agent rows with per-parent folding, chevron markers, and indentation, plus CLI diagnostics for sub-agent relationships.

Changes

Provider and Session Schema Updates

Layer / File(s) Summary
SessionMeta Schema Extension
core/src/session.rs, core/src/providers/claude.rs, core/src/providers/codex.rs, core/src/providers/hermes.rs, core/src/providers/omp.rs, core/src/providers/qwen.rs
SessionMeta struct adds parent_id: Option<String> and child_count: usize fields. All five providers are updated to populate these fields when constructing session metadata.
OMP Child-Transcript Discovery
core/src/providers/omp.rs
New public function list_children(parent: &SessionMeta) -> Vec<SessionMeta> discovers and lazily loads child transcripts from a sibling directory. Children are enumerated from .jsonl files, stamped with parent linkage and derived unique ids, and sorted by recency. Tests verify correct child counting, linkage, and cwd inheritance.
Session Module Router
core/src/session.rs
Public function list_children(meta: &SessionMeta) delegates to provider-specific implementations. For Agent::Omp, calls providers::omp::list_children; for other agents returns an empty vector.

TUI Sub-agent UI Implementation

Layer / File(s) Summary
App State and ListRow Types
tui/src/tui.rs
App gains expanded_parents (tracking which parent sessions have visible sub-agents) and children cache (lazy-loaded sub-agent metadata per parent id). ListRow enum adds a SubAgent variant. Initialization and auto-refresh manage cache lifecycle.
List Generation and Sub-agent Navigation
tui/src/tui.rs
list_rows() appends sub-agent rows when parents are expanded. New helpers: push_subagent_rows renders SubAgent rows with placeholders; meta_by_sid resolves metadata for top-level and sub-agent sessions; toggle_subagents lazily populates children and updates fold state. toggle_expand_at_selection routes Solo/Child sessions to sub-agent folding. load_preview uses meta_by_sid for correct transcript loading.
Sub-agent Row Rendering and Indentation
tui/src/tui.rs
draw_list captures fold state and cache for rendering. List item mapping routes rows through list_item_for_session with an Indent enum (Top/Child/SubAgent) instead of a boolean. Connector glyphs, cwd visibility, and chevrons vary by tier. draw_detail uses meta_by_sid for correct sub-agent session titles.

Diagnostic Output

Layer / File(s) Summary
Dry-run Sub-agent Diagnostics
tui/src/main.rs
--dry-run now reports OMP sub-agent parent/child totals and probes the first parent by printing child counts and metadata.

Sequence Diagram

sequenceDiagram
  participant User as User Selection
  participant TUI as tui.rs
  participant SessionMod as session.rs
  participant OMP as providers/omp.rs
  participant Render as List Renderer

  User->>TUI: toggle_expand_at_selection (Solo/Child)
  TUI->>TUI: toggle_subagents(parent_sid)
  TUI->>SessionMod: list_children(parent_meta)
  SessionMod->>OMP: list_children(parent_meta)
  OMP->>OMP: discover sibling JSONL files
  OMP-->>SessionMod: Vec<SessionMeta> (children)
  SessionMod-->>TUI: children
  TUI->>TUI: cache children, update expanded_parents
  TUI->>TUI: list_rows()
  TUI->>TUI: push_subagent_rows(parent, expanded)
  TUI->>Render: SubAgent rows with Indent::SubAgent
  Render->>Render: list_item_for_session(Indent::SubAgent)
  Render-->>User: display with child chevrons and counts
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • rollysys/auditui#25: Both PRs modify core/src/providers/omp.rs; the referenced PR introduces the OMP provider while this PR extends it with parent/child session discovery and lazy-loading via the new public list_children function.

Poem

🐰 Nested sessions hop and fold,
Parent scripts hold the young and bold,
Chevrons flip when branches grow,
Sub-agents dance in rowsy show!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature: adding browsing of oh-my-pi sub-agent transcripts with hierarchical/fold visualization under parents.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/omp-subagent-transcripts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Sub-agents are shown as a second indentation tier under the session that
spawned them, collapsed by default. The existing group-fold machinery is
reused: a new `ListRow::SubAgent` variant, an `expanded_parents` set, and a
lazily-populated `children` map keyed by parent id.

- A parent that spawned sub-agents shows a `▶N⤷` / `▼N⤷` fold marker with the
  child count; Space (or Enter on a group header) toggles it.
- Expanding a parent lazily calls `session::list_children` once and caches the
  result, so the hundreds of child files are never read at startup.
- `meta_by_sid` resolves a selected sub-agent to its meta (searching the
  children cache), so preview/detail render the sub-agent transcript via the
  normal read_transcript path — no special-casing downstream.
- Auto-refresh clears the children cache and fold state (child_count/parentage
  may have shifted).
- `--dry-run` gains an omp sub-agent diagnostic (host count, total children,
  and a live list_children probe) — used to smoke-test against real data.

Verified end-to-end against a real ~/.omp sample: parent indexed at top level,
child_count=2 counted, list_children loaded both with correct slug-based ids.

Co-Authored-By: Claude <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tui/src/tui.rs (1)

1417-1420: ⚖️ Poor tradeoff

Consider refactoring to avoid cloning the groups vector.

Cloning self.groups().to_vec() to work around the borrow checker copies potentially hundreds of SessionGroup instances on every list render. The clone is necessary because groups() takes &mut self (to populate the cache), and then push_subagent_rows() needs &self to access children.

Possible alternatives:

  • Cache the groups vector at a higher level to avoid the &mut self borrow in this path
  • Restructure to build rows without needing simultaneous access to groups and children
  • Split groups() into separate "get cached" and "compute if needed" methods

This is a minor performance concern—the code is correct—but worth addressing if rendering performance becomes noticeable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tui/src/tui.rs` around lines 1417 - 1420, The code clones
self.groups().to_vec() to avoid a mutable borrow while later calling
push_subagent_rows which only needs an immutable view, causing expensive copies
of SessionGroup; change the API so you can access groups without requiring &mut
self here — e.g., add a groups_cached(&self) -> &[SessionGroup] accessor (or
split groups() into cached_get() and compute_if_missing(&mut self) variants) or
compute/populate the cache earlier so this path can call groups_cached() and
then call push_subagent_rows(&self, groups_slice, &expanded_parents) to avoid
cloning; update references to expanded_parents, groups(), push_subagent_rows(),
SessionGroup, and children accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tui/src/tui.rs`:
- Around line 1492-1494: The synchronous call to session::list_children(&parent)
on the UI thread blocks the TUI for large parents; replace it with an
asynchronous load: when a parent is expanded set a loading state in the node (so
the UI shows a spinner), then spawn a background thread/task (analogous to the
existing cache-warming background work) to call session::list_children(parent)
off the UI thread, send the resulting Vec back to the UI via a channel, and on
the main thread update self.children.insert(sid.to_string(), kids) and clear the
loading state and trigger a re-render when the channel message arrives; remove
the direct synchronous call from the expand handler so the UI never blocks.

---

Nitpick comments:
In `@tui/src/tui.rs`:
- Around line 1417-1420: The code clones self.groups().to_vec() to avoid a
mutable borrow while later calling push_subagent_rows which only needs an
immutable view, causing expensive copies of SessionGroup; change the API so you
can access groups without requiring &mut self here — e.g., add a
groups_cached(&self) -> &[SessionGroup] accessor (or split groups() into
cached_get() and compute_if_missing(&mut self) variants) or compute/populate the
cache earlier so this path can call groups_cached() and then call
push_subagent_rows(&self, groups_slice, &expanded_parents) to avoid cloning;
update references to expanded_parents, groups(), push_subagent_rows(),
SessionGroup, and children accordingly.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 14e9708f-7078-4696-bad2-6329509eff62

📥 Commits

Reviewing files that changed from the base of the PR and between 2879008 and c525951.

📒 Files selected for processing (8)
  • core/src/providers/claude.rs
  • core/src/providers/codex.rs
  • core/src/providers/hermes.rs
  • core/src/providers/omp.rs
  • core/src/providers/qwen.rs
  • core/src/session.rs
  • tui/src/main.rs
  • tui/src/tui.rs

Comment thread tui/src/tui.rs
Comment on lines +1492 to +1494
if !self.children.contains_key(sid) {
let kids = session::list_children(&parent);
self.children.insert(sid.to_string(), kids);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Synchronous I/O on the UI thread will freeze the TUI when expanding large parents.

session::list_children(&parent) reads and parses child transcript files from disk synchronously. According to the PR objectives, real-world parents can spawn 900+ children. Reading and summarizing that many jsonl files on the main thread will block the UI for seconds, leaving the user unable to interact with the TUI.

The lazy-loading strategy (defer until expansion) is good for startup performance, but the implementation should be asynchronous:

  1. Show a loading indicator when the user expands a parent
  2. Spawn the load in a background thread (similar to the existing cache warming at line 555-563)
  3. Send the results back via a channel
  4. Update self.children and re-render when results arrive

Without async loading, expanding large parents creates a poor user experience.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tui/src/tui.rs` around lines 1492 - 1494, The synchronous call to
session::list_children(&parent) on the UI thread blocks the TUI for large
parents; replace it with an asynchronous load: when a parent is expanded set a
loading state in the node (so the UI shows a spinner), then spawn a background
thread/task (analogous to the existing cache-warming background work) to call
session::list_children(parent) off the UI thread, send the resulting Vec back to
the UI via a channel, and on the main thread update
self.children.insert(sid.to_string(), kids) and clear the loading state and
trigger a re-render when the channel message arrives; remove the direct
synchronous call from the expand handler so the UI never blocks.

apply_refreshed_sessions cleared `expanded_parents` and the `children` cache
on every refresh, so each 30s auto-refresh tick collapsed any open sub-agent
fold and reset the detail pane back to the parent — making sub-agents look
"merged into one / impossible to view separately" while idly reading.

Keep folds open across a refresh; only drop cache/fold entries for parents
that vanished. We deliberately do not re-list children of still-expanded
parents here, so a parent with hundreds of children never re-scans its
directory on a tick (new children surface on the next manual collapse+expand).

Verified with a tmux-driven real-data run: expand a 6-child host, select a
sub-agent, cross several auto-refresh ticks — fold stays open, selection and
detail stay put.

Also extend the `--dry-run` omp probe to report uniq_ids / uniq_paths and the
first few child id/file/prompt, which is how the id/path uniqueness was
confirmed.

Co-Authored-By: Claude <noreply@anthropic.com>
@rollysys

rollysys commented Jun 8, 2026

Copy link
Copy Markdown
Owner Author

Update: fixed a fold-collapse-on-refresh bug + real-data validation

Added 89e8180:

Bug: apply_refreshed_sessions cleared expanded_parents + the children cache on every refresh, so each auto-refresh tick collapsed any open sub-agent fold and reset the detail pane to the parent — sub-agents looked "merged into one / can't view separately" while idly reading. Fixed by keeping folds open across a refresh and only dropping entries for parents that vanished (no re-listing still-expanded parents, so a 918-child host never re-scans its dir on a tick).

Validated on dev against the real 918-child session (tmux-driven, fresh binary built in place):

  • Expand the parent → all 918 sub-agents list individually (distinct slug labels: PercPncTemplateTest, PercPnc1, SP21, Fdiv15, …)
  • Select one → detail renders that sub-agent's own transcript (correct model, USER/THINK/TOOL flow)
  • Cross an auto-refresh tick (group ts visibly updated) → fold stays open, selection + scroll unchanged

Also verified the smaller 6-child host end-to-end the same way.

@rollysys
rollysys merged commit 6294d0a into main Jun 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant