Skip to content

feat(bub): capture long-running agent events - #1230

Open
PsiACE wants to merge 2 commits into
rfc/long-horizon-workloadsfrom
feat/e2e-bub-capture
Open

feat(bub): capture long-running agent events#1230
PsiACE wants to merge 2 commits into
rfc/long-horizon-workloadsfrom
feat/e2e-bub-capture

Conversation

@PsiACE

@PsiACE PsiACE commented Aug 13, 2026

Copy link
Copy Markdown
Member

Stack

  1. #1229 RFC: long-horizon workloads
  2. #1230 Bub event capture
  3. #1231 Workload catalog
  4. #1232 Long-horizon Bub runtime

Merge in this order. This PR depends on #1229.

Which issue or RFC does this PR close?

Implements the Bub capture layer of #1229.

Rationale for this change

Long-running acceptance workloads need durable evidence without persisting credentials or adding harness-specific provider configuration.

What changes are included in this PR?

  • Add opt-in Bub event capture and checkpoints.
  • Redact sensitive values at the client boundary.
  • Use Bub's native settings instead of duplicating provider keys.
  • Add behavior and security regression coverage.

Are there any user-facing changes?

Capture is opt-in. Existing Bub behavior and configuration remain unchanged.

How was this change tested?

  • make harness-sync
  • make harness-check

AI usage statement

OpenAI Codex (GPT-5) assisted with implementation and tests. The author is responsible for the submitted changes.

@PsiACE
PsiACE marked this pull request as ready for review August 13, 2026 11:10
Copilot AI lite review requested due to automatic review settings August 13, 2026 11:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds an opt-in Bub-side “capture layer” that records long-running agent trajectory events (user prompt, LLM results, tool results) as bounded PowerContext Content Sources, periodically checkpointing them through the Memory pipeline and redacting sensitive values at the client boundary.

Changes:

  • Switch Bub integration configuration to Bub’s Pydantic settings (PowerContextSettings) and wire capture/checkpoint options into the plugin.
  • Capture and flush Bub events as Content Sources with JSONL optional capture logging and secret redaction.
  • Add an e2e regression test for credential redaction and update integration dependencies/docs.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
integrations/bub/src/powercontext_bub/plugin.py Implements Pydantic-backed settings, event capture/checkpointing, and redaction utilities.
integrations/bub/src/powercontext_bub/init.py Exposes PowerContextSettings alongside the plugin.
integrations/bub/README.md Documents opt-in capture behavior and new configuration options.
integrations/bub/pyproject.toml Adds pydantic-settings dependency for Bub settings integration.
e2e/bub/uv.lock Locks the new dependency for the Bub e2e environment.
e2e/bub/tests/test_bub_capture.py Adds regression coverage for credential redaction in captured tool results.
Suppressed comments (1)

integrations/bub/src/powercontext_bub/plugin.py:393

  • Same isinstance(value, list | tuple) issue here: it will raise TypeError when walking Codex auth JSON arrays, which can break secret redaction (and therefore capture) at runtime. Use (list, tuple) instead.
def _sensitive_values(value: Any, *, sensitive: bool = False) -> set[str]:
    if isinstance(value, dict):
        secrets: set[str] = set()
        for key, item in value.items():
            secrets.update(_sensitive_values(item, sensitive=sensitive or _is_sensitive_key(str(key))))
        return secrets
    if isinstance(value, list | tuple):
        secrets = set()
        for item in value:
            secrets.update(_sensitive_values(item, sensitive=sensitive))
        return secrets
    if sensitive and isinstance(value, str) and len(value) >= 8:
        return {value}
    return set()

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

timeout=float(os.getenv("POWERCONTEXT_BUB_TIMEOUT", "10")),
max_bytes=max_bytes,
)
base_url: HttpUrl = HttpUrl("http://127.0.0.1:8000")
Comment on lines +344 to +351
def _sanitize(value: Any) -> Any:
if isinstance(value, dict):
return {
str(key): "[REDACTED]" if _is_sensitive_key(str(key)) else _sanitize(item) for key, item in value.items()
}
if isinstance(value, list | tuple):
return [_sanitize(item) for item in value]
return value
Comment on lines +371 to +377
def _codex_auth_secrets() -> set[str]:
codex_home = Path(os.getenv("CODEX_HOME", str(Path.home() / ".codex"))).expanduser()
try:
auth = json.loads((codex_home / "auth.json").read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return set()
return _sensitive_values(auth)
Comment on lines +60 to +64
assert len(captured_requests) == 1
request = captured_requests[0]
assert request.metadata["event"] == "tool_result"
assert sensitive_value not in request.content
assert "[REDACTED]" in request.content
Comment on lines 66 to +70
def load_state(self, message: Any, session_id: str) -> TurnState:
del message, session_id
return {
STATE_KEY: {
"base_url": self.settings.base_url,
"scope_id": self.settings.scope_id,
"base_url": self.base_url,
Copilot AI review requested due to automatic review settings August 13, 2026 11:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 6 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

integrations/bub/src/powercontext_bub/plugin.py:70

  • load_state discards the session_id Bub provides, but event capture later falls back to state.get("session_id", "unknown"). If Bub doesn’t inject session_id into state, captured events will all be tagged with "unknown" (and source_id uniqueness degrades across sessions). Persist the session_id in the returned state so capture metadata is stable without callers patching state manually.
    def load_state(self, message: Any, session_id: str) -> TurnState:
        del message, session_id
        return {
            STATE_KEY: {
                "base_url": self.base_url,

integrations/bub/src/powercontext_bub/plugin.py:155

  • save_state performs only a single flush. Since flush_memory is explicitly bounded, one call may not advance current_cursor to the latest captured position, leaving some captured Sources unprocessed when the session ends. Consider retrying flush until the cursor reaches the captured high-watermark (or no further progress is possible), with a small upper bound to avoid infinite loops.
        if not self.settings.capture_events:
            return
        async with self._capture_lock:
            await self._flush_captured_sources(state, final=True)

e2e/bub/tests/test_bub_capture.py:58

  • This test sets BUB_API_KEY to the same sensitive_value that is passed in tool arguments and echoed in ToolCallResult.result, so it can still pass even if key-based redaction of arguments regresses (because env-value redaction would remove the sentinel from the serialized content). Use a different env value and avoid embedding the argument sentinel in result so the test specifically verifies argument-key redaction at the client boundary.
    settings = PowerContextSettings(
        base_url="http://127.0.0.1:8000",
        scope_id="test:scope",
        capture_events=True,
        capture_checkpoint_every=100,

Comment on lines +344 to +351
def _sanitize(value: Any) -> Any:
if isinstance(value, dict):
return {
str(key): "[REDACTED]" if _is_sensitive_key(str(key)) else _sanitize(item) for key, item in value.items()
}
if isinstance(value, list | tuple):
return [_sanitize(item) for item in value]
return value
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.

2 participants