Skip to content

add Poolside Agent CLI pool agent support - #1241

Merged
mjacobs merged 13 commits into
kenn-io:mainfrom
scross01:poolside
Jul 24, 2026
Merged

add Poolside Agent CLI pool agent support#1241
mjacobs merged 13 commits into
kenn-io:mainfrom
scross01:poolside

Conversation

@scross01

Copy link
Copy Markdown
Contributor

This PR adds agent support for the Poolside Agent CLI pool

Tested on MacOS and Linux.

@scross01
scross01 marked this pull request as draft July 23, 2026 12:13
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (9786e3f)

Medium-severity issues remain in Poolside discovery/parsing and tool-call termination handling.

Medium

  • internal/parser/termination.go:147 — Any non-empty ResultEvents slice resolves a tool call, including running-only Codex subagent events. This defeats the preceding status check and regresses TerminationToolCallPending.

    • Fix: Remove the blanket len(tc.ResultEvents) > 0 fallback; the preceding loop already recognizes terminal or statusless result events.
  • internal/parser/poolside_provider.go:48 — Discovery converts every os.ReadDir failure into a successful empty result, so transient permission or I/O errors can trigger authoritative source-missing tombstones. It also materializes the entire trajectory archive.

    • Fix: Use WithStreamingFileDiscovery and streamDirectoryEntries, propagating errors other than IsNotExist.
  • internal/parser/poolside.go:195 and internal/parser/poolside.go:660 — Parsing and fingerprinting reject NDJSON records larger than 1 MB. Large tool observations or assistant output can therefore make an entire session fail synchronization.

    • Fix: Support larger parser records and fingerprint the raw file with io.Copy instead of a line-limited scanner.
  • internal/parser/poolside.go:504StartedAt comes from the first inference-end timestamp, or the final event when no inference exists, instead of the session’s first event. This shortens session durations or makes them zero, skewing analytics.

    • Fix: Track the first valid event timestamp—preferably session.start—separately from lastEventTime.

Reviewers: 2 done | Synthesis: codex, 11s | Total: 6m7s

scross01 added 2 commits July 23, 2026 09:26
- termination.go: remove blanket ResultEvents skip that defeated
  running-only event detection, regressing TerminationToolCallPending
- poolside_provider.go: use streaming discovery instead of os.ReadDir
  to propagate I/O errors instead of silently returning empty results
- poolside.go: increase scanner buffer to 10MB for large NDJSON records
  and use io.Copy for fingerprinting instead of line-limited scanner
- poolside.go: track firstEventTime from first valid timestamp instead
  of firstInferenceTime for correct StartedAt computation
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (9ce7d50)

Poolside support needs changes before approval: four medium-severity parser correctness and robustness issues remain.

Medium

  • internal/parser/poolside.go:450 — Peak context counts only input_tokens, excluding cache-read and cache-write tokens. This understates session peak context and conflicts with the canonical calculation. Include both cache fields or derive aggregates with UsageEventTokenAggregate.

  • internal/parser/poolside.go:493session.error events are ignored, leaving exitReason empty and incorrectly classifying errored sessions as clean. Map these events to an appropriate non-clean termination status and add a regression test.

  • internal/parser/poolside.go:205 — Malformed NDJSON lines are silently discarded without updating MalformedLines or detecting an incomplete final record. Count malformed lines and mark an unterminated malformed final line as truncated, consistent with other JSONL parsers.

  • internal/parser/poolside.go:196 — Records are capped at 10 MiB despite potentially large tool-result observations and the shared 64 MiB parser limit. This can fail an entire session with bufio.ErrTooLong. Use the shared maxLineSize limit or the repository’s reusable line reader.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 5m57s

- peak context now includes cache_read and cache_write tokens for
  accurate session peak context computation
- session.error events now set exit reason to trigger truncated
  termination status instead of incorrectly classifying as clean
- malformed NDJSON lines are counted in MalformedLines field
- replaced bufio.Scanner with newLineReader using maxLineSize (64MB)
  to handle large tool-result observations without failing entire
  sessions
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (d8cea65)

Medium-severity issue found: tool-only Poolside assistant turns lose model attribution.

Medium

  • internal/parser/poolside.go:258 — Model attribution occurs only when assistant_message_end.assistant_message is non-empty. Tool-only assistant turns therefore lose their known model, skewing model analytics. Resolve and stamp the model independently of textual content, and add a test for an empty tool-calling assistant turn.

Reviewers: 2 done | Synthesis: codex, 8s | Total: 7m43s

The model attribution in assistant_message.end was nested inside
AssistantMessage != "", so tool-only assistant turns (no text
content) lost their model. Extract the attribution to run whenever
the last message is an assistant, regardless of content emptiness.

Add TestParsePoolsideSessionToolOnlyModelAttribution to cover the
empty-content tool-calling case.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (ee43470)

Poolside support is generally sound, but remote sync may expose unrelated application data.

Medium

  • Overbroad remote-sync rootinternal/parser/types.go:806
    Poolside’s configured root is the entire application-data directory, although only trajectories/ is parsed. Provider-authoritative file-backed roots are recursively archived during remote sync, so this may transfer unrelated configuration, caches, or credentials.

    Fix: Point the configured root directly to trajectories/, or add Poolside-specific file-scoped handling to both HTTP and SSH remote-sync resolvers so only discovered trajectory files are exported, with parity tests.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 9m17s

Poolside's configured root is the entire application-data directory,
but only trajectories/ is parsed. Provider-authoritative file-backed
roots are recursively archived during remote sync, so the broad root
could transfer unrelated configuration, caches, or credentials.

Add resolvePoolsideTarget that resolves the root to only the
trajectories/ subdirectory. Add matching av_emit_poolside_target in
the SSH resolve script. Both HTTP and SSH resolvers now skip Poolside
roots without an existing trajectories/ directory.

Add parity tests in both remotesync and ssh packages to verify the
narrowing behavior and the skip-when-missing path.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (d63a38d)

Remote Poolside imports are broken due to a provider-root mismatch.

Medium

  • internal/remotesync/resolve.go:67, internal/ssh/resolve.go:162 — Remote sync passes the trajectories/ directory as Poolside’s provider root, while the provider searches <root>/trajectories/. This resolves to trajectories/trajectories/, silently discovers no remote sessions, and may tombstone previously imported sessions.
    • Fix: Support a direct trajectories/ provider root or separate archive scope from provider root. Add an end-to-end remote import test.

Reviewers: 2 done | Synthesis: codex, 7s | Total: 8m18s

Remote sync passes the trajectories/ subdirectory as Poolside's
provider root, but poolsideDiscoverEach always appended
trajectories/ to the root, resolving to trajectories/trajectories/
and discovering no files.

Add poolsideTrajectoriesDir helper that returns the root as-is when
its base component is already 'trajectories', otherwise appends
trajectories/. Refactor poolsideDiscoverEach, poolsideWatchRoots,
poolsideClassifyPath, and poolsideFindFile to use it.

Add TestPoolsideTrajectoriesDir unit tests covering both root forms.
Extend the SSH/HTTP parity test to include Poolside and verify both
resolvers narrow to the trajectories/ subdirectory.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (bb21b42)

Poolside integration needs two medium-severity fixes before approval.

Medium

  • internal/parser/poolside.go:627 — The orphaned-tool check runs before exit_tool_called handling, so a normal session ending with Poolside’s terminal exit tool may be misclassified as tool_call_pending when no result event follows. Treat exit_tool_called or a trailing exit tool as clean before checking for orphaned calls, and add a regression test covering an unresolved terminal exit call.

  • internal/remotesync/resolve.go:325, internal/ssh/resolve.go:165 — Both remote-sync resolvers always append trajectories, even when the configured root already points to that directory. This produces trajectories/trajectories and omits Poolside sessions from remote sync. Normalize direct trajectories roots to use them as-is in both resolvers, with tests for configured trajectory roots.


Reviewers: 2 done | Synthesis: codex, 8s | Total: 8m16s

…alization

Two medium-severity issues from review:

1. classifyPoolsideTermination checked hasOrphanedToolCall before
   exit_tool_called, misclassifying normal Poolside exits (where the
   exit tool has no result event because the session terminates) as
   tool_call_pending. Check exit_tool_called first so trailing exit
   tools are classified as clean.

2. Both remote-sync resolvers always appended trajectories/ even when
   the configured root was already a trajectories directory, producing
   trajectories/trajectories and omitting sessions. Normalize direct
   trajectories roots to use them as-is in both HTTP and SSH resolvers.

Add regression tests for both fixes.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (bbc256b)

Changes need fixes: two medium-severity parser correctness issues were found.

Medium

  • internal/parser/poolside.go:296 — Each thought.end overwrites ThinkingText, so assistant turns with multiple inference/tool steps retain only the final reasoning block. Append each thought with a separator and add a regression test covering multiple thought.end events in one assistant message.

  • internal/parser/poolside.go:206 — A malformed final NDJSON record is counted but does not set IsTruncated or affect termination classification, causing a trajectory cut off mid-write to appear clean. Track whether the final nonblank record is malformed and give that truncation state precedence when setting IsTruncated and TerminationStatus.


Reviewers: 2 done | Synthesis: codex, 7s | Total: 10m41s

Three correctness issues from review:

1. thought.end overwrote ThinkingText instead of appending. Multiple
   reasoning blocks in one assistant turn now concatenate with newline,
   matching qwen/kimi/zcode/posit parsers.

2. Malformed final NDJSON records were counted but never set
   IsTruncated or TerminationStatus. A trajectory cut off mid-write
   now correctly sets IsTruncated and returns TerminationTruncated,
   taking precedence over other termination signals.

3. TestPoolsideTrajectoriesDir used hardcoded Unix paths that failed
   on Windows. Use filepath.FromSlash in assertions. Add Windows skip
   to TestResolveScriptPoolsideTrajectoriesRoot (POSIX shell script
   test, same pattern as existing skips).
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (e5eb75c)

The Poolside changes are generally sound, but one medium-severity parser correctness issue should be fixed.

Medium

  • internal/parser/poolside.go:371pendingToolCalls stores only one call per step_id. Multiple parsed calls in the same step overwrite one another, causing results to be attached to the wrong call or discarded. Pair calls using the payload call ID, or maintain a per-step queue with ID/order fallback, and add a regression test covering multiple calls within the same step.

Reviewers: 2 done | Synthesis: codex, 6s | Total: 9m47s

pendingToolCalls was keyed by step_id, so multiple tool_call.parsed
events sharing the same step_id overwrote each other. Results were
then attached to only the last parsed call, discarding the rest.

Key pendingToolCalls by the payload call ID (shared between parsed
and result events) so each call pairs with its own result
independently.

Add TestParsePoolsideMultipleCallsSameStep covering two reads in
the same step with distinct results.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (9b7e6cf)

Poolside support is functionally promising, but two medium-severity issues should be addressed before merge.

Medium

  • internal/parser/poolside_provider.go:142 — Every Poolside fingerprint hashes the entire trajectory without a freshness gate. Periodic sync therefore rereads all historical trajectories, making background work scale with total archive size. Add a size/mtime/data-version fast path so only changed sources are hashed, plus a small-versus-large archive cardinality regression test.

  • internal/parser/poolside.go:341pendingShellCmds is keyed by step_id, so multiple shell calls sharing a step overwrite one another. Returned shell IDs and subsequent shell_status, shell_tail, or shell_kill calls can then be associated with the wrong command. Key pending commands by the payload call ID (tc.ID/tr.ID), matching the primary tool-result pairing, and remove entries after use.


Reviewers: 2 done | Synthesis: codex, 11s | Total: 11m38s

Two medium-severity issues from review:

1. poolsideFingerprintSource hashed the entire trajectory on every
   sync, making background work scale with total archive size. Change
   to stat-only (size/mtime), deferring the expensive content hash
   to parse time. The engine's skip cache uses MTimeNS for freshness
   and already handles empty hashes gracefully.

2. pendingShellCmds was keyed by step_id, so multiple shell calls
   sharing a step overwrote each other. Subsequent shell_status,
   shell_tail, or shell_kill calls could then be enriched with the
   wrong command. Key by payload call ID instead and delete after
   use.

Add TestParsePoolsideMultipleShellCallsSameStep covering two shell
calls in the same step with distinct shell_ids and commands.
@roborev-ci

roborev-ci Bot commented Jul 23, 2026

Copy link
Copy Markdown

roborev: Combined Review (3aaf298)

No Medium, High, or Critical findings were identified.


Reviewers: 2 done | Synthesis: codex, 6s | Total: 8m35s

@scross01
scross01 marked this pull request as ready for review July 23, 2026 21:39
mjacobs added 2 commits July 23, 2026 23:41
The evidence entry claimed no public source exists, but the pool
release repository (github.com/poolsideai/pool) and the Poolside Agent
CLI documentation (docs.poolside.ai/cli/pool) are both public and were
linked from the pull request itself. Upstream confirms automatic
session saving and per-session trajectory files, though it publishes
neither the on-disk paths nor the NDJSON event schema, so the entry is
now evidence-tier documentation with the schema still characterized
from real trajectory files.
@roborev-ci

roborev-ci Bot commented Jul 24, 2026

Copy link
Copy Markdown

roborev: Combined Review (c1dc208)

Medium-severity issue found in Poolside SSH sync targeting.

Medium

  • internal/ssh/resolve.go:283 — Poolside has three default directories, so a non-empty POOLSIDE_DIR override is emitted once per default. Because directory targets are not deduplicated, SSH sync may archive and transfer the entire trajectories directory three times. Emit the environment override only once, or deduplicate targets in parseResolvedTargets or buildTarCommand.

Reviewers: 2 done | Synthesis: codex, 5s | Total: 9m29s

@mjacobs
mjacobs merged commit 9145afd into kenn-io:main Jul 24, 2026
16 checks passed
mariusvniekerk pushed a commit that referenced this pull request Jul 24, 2026
…de (#1258)

Poolside stores trajectory files under the XDG state directory (~/.local/state/poolside/trajectories/), not the data directory. The merged PR #1241 used the wrong path, preventing session discovery.

Co-authored-by: Stephen Cross <scross01@users.noreply.github.com>
mjacobs added a commit that referenced this pull request Jul 24, 2026
Poolside support in #1241 registered three platform-specific default directories behind the single `POOLSIDE_DIR` override. The SSH resolver evaluates that override while iterating each default, so a non-empty override emits the same `trajectories` target three times. The Linux fallback correction in #1258 changed one default path but did not cause or resolve this duplication.

Deduplicate resolved directory targets per agent before archive construction, preserving first-seen order and allowing different agents to share a path independently. Strengthen the Poolside override regression to require one parsed transfer target. This addresses the post-merge roborev finding on #1241.

Co-authored-by: Matthew Jacobs <mjacobs@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants