Skip to content

Live Stream: derive Anchor-owned file artifacts#6207

Open
franksong2702 wants to merge 58 commits into
nesquena:masterfrom
franksong2702:franksong2702/live-stream-artifact-reference-derivation
Open

Live Stream: derive Anchor-owned file artifacts#6207
franksong2702 wants to merge 58 commits into
nesquena:masterfrom
franksong2702:franksong2702/live-stream-artifact-reference-derivation

Conversation

@franksong2702

Copy link
Copy Markdown
Contributor

Thinking Path

  • The Live-to-Final contract says produced files must remain owned by and findable from the assistant turn that created them.
  • The Anchor model already classifies artifact_reference, and Live Stream: preserve Anchor-owned side effects #6204 carries Anchor-owned artifacts through scene settlement and reload.
  • Production runs still emit no artifact-reference event, so there is nothing for that ownership path to persist.
  • Deriving success in the browser is unsafe because the modern structured tool_complete SSE currently hardcodes is_error: false.
  • The WebUI backend still holds the complete result, so it is the narrowest place to prove the mutation, bound its metadata, and emit a journaled event.

What Changed

  • Add a pure production derivation module for canonical Hermes write_file and patch results.
  • Require the result schema to prove the mutation landed and fail closed for malformed, unknown, failed, or read-only tools.
  • Prefer result-reported paths, normalize them to the session workspace, and reject traversal, symlink escapes, foreign drive paths, URLs, and ignored build/cache trees.
  • Emit one path-only artifact_reference SSE event per unique file after tool_complete; raw args and result bodies are never included.
  • Keep proven artifact references journalable when a file write finishes just after cancellation.
  • Route the new event into the active Assistant Turn Anchor without adding a Worklog row or repainting the live scene.
  • Add focused producer, path-safety, ordering, replay-cursor, cancellation, and frontend-routing coverage.
  • Update the Live-to-Final and Stable Assistant Turn Anchor docs with the production event boundary.

Fixes #6205.
Refs #3400.
Depends on #6204 for artifacts[] scene projection, settlement persistence, and reload hydration.

Why It Matters

A file mutation is no longer inferred from a completed-looking tool card. The producer proves that the write landed, emits only safe path metadata, and gives the artifact the same Assistant Turn owner as the tool that produced it. Together with #6204, that ownership survives the Live-to-Final transition without changing the readable Worklog.

Release note: Preserve turn-owned file artifact references from successful write_file and patch calls across live streaming and replay.

Verification

  • Red-first gate before implementation: tests/test_live_anchor_artifact_reference.py failed all 3 initial integration checks.
  • Final focused regression command across 25 artifact, Anchor, scene-persistence, run-journal, reconnect, and cancellation files: 356 passed.
  • node --check static/messages.js
  • python3 -m py_compile api/artifact_references.py api/streaming.py
  • Runtime ESLint guard using the installed repository binary: passed with no findings.
  • python3 scripts/ruff_lint.py --diff origin/master: 0 findings on added/modified lines.
  • git diff --check
  • git merge-tree --write-tree HEAD 950b71201442cd46876ca7d6b11c16fc846e2a50: clean composition with Live Stream: preserve Anchor-owned side effects #6204.

Risks / Follow-ups

  • This intentionally recognizes only the Hermes Agent canonical mutation tools (write_file, patch). Shell/code execution and dynamic MCP mutation inference remain out of scope because their side effects cannot be derived reliably from names or command text.
  • The existing Artifact tab keeps its current reconstruction behavior; this PR adds turn ownership metadata rather than a new UI.
  • Paths outside the session workspace fail closed even if a tool reports that it wrote there; they cannot be opened safely through the session-scoped Artifact surface.
  • The modern tool_complete.is_error field remains a separate, broader tool-card correctness issue; artifact derivation does not trust it.
  • Claude Code 2.1.212 was invoked for a tool-less independent diff review, but its authenticated CLI returned 403 Request not allowed before producing review content. It is not represented as a completed review.

Model Used

  • OpenAI Codex (GPT-5 coding agent; exact runtime model ID is not exposed in this client) implemented and self-reviewed the change.
  • A read-only Codex subagent audited Hermes Agent result schemas, mutation tools, SSE fields, open-PR overlap, and test boundaries.
  • Claude Code 2.1.212 review was attempted as described above but did not execute because of the CLI 403.

Contract Routing

Task type: hardening an already accepted Live-to-Final ownership contract; no product contract is removed or reversed.

Touched areas:

Relevant public docs:

  • AGENTS.md
  • CONTRIBUTING.md
  • docs/CONTRACTS.md
  • docs/rfcs/live-to-final-assistant-replies.md
  • docs/rfcs/stable-assistant-turn-anchors.md
  • docs/architecture/stable-assistant-turn-anchor-phase0.md

State-layer invariant: proven raw tool result -> bounded workspace-relative artifact_reference SSE -> run journal -> active Assistant Turn Anchor -> renderer-neutral activity_scene_v1 through #6204, while remaining absent from Compact Worklog rows.

Scope boundaries: no rich artifact UI, no terminal/code/MCP guessing, no raw file/tool-result persistence, and no change to final-answer rendering.

Contract Change

Previous implementation state: artifact_reference was documented and classified by the Anchor model, but production file mutations emitted no such event.

New implementation state: successful canonical file mutations emit a separate path-only event from the backend after the result proves the write landed.

Compatibility: additive event metadata only. Existing tool cards, Artifact tab behavior, final answers, and Compact Worklog rendering remain unchanged.

@greptile-apps

greptile-apps Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a server-side production path that derives artifact_reference SSE events from proven write_file and patch results, emits them after tool_complete, and reconciles them into the terminal Assistant Turn Anchor scene — giving file artifacts the same turn ownership as the tool that produced them and making that ownership survive the Live-to-Final transition.

  • Backend (artifact_references.py, streaming.py, routes.py): a new pure derivation module proves mutations via result-field inspection, applies path safety (traversal, symlink, URL, drive-path, and ignored-tree rejection), enforces byte/count budgets, and emits one path-only SSE event per unique file; the anchor scene persistence endpoint is extended to merge existing and incoming scenes with three-field ownership validation and owner_authority gating.
  • Frontend (messages.js, assistant_turn_anchors.js, sessions.js): the artifact_reference SSE event is routed to the active Anchor without repainting the live scene; scene hydration replays persisted artifact events; the bounded-artifact helpers enforce the same count/byte budget in the browser; hasAnchorActivityScene is widened to treat artifact-only scenes as valid for reconnection.
  • Tests: new and extended test files cover producer derivation, path-safety matrix, ordering guarantees, replay-cursor advancement, post-cancellation journaling, foreign-owner rejection, budget trimming, and frontend routing.

Confidence Score: 5/5

Safe to merge; the change is additive and fails closed — unknown tools, failed results, workspace-escaping paths, and malformed ownership all produce zero artifact events.

The derivation module is pure and fails closed by design. Path safety is enforced through Path.resolve() plus relative_to which correctly rejects traversal and symlink escapes. Ownership validation runs at both the server writeback and browser settlement layers. Test coverage is extensive across derivation, routing, cancellation, and persistence. The two style observations do not affect correctness.

No files require special attention; the streaming.py and routes.py changes are the most complex but are well-covered by the new integration tests.

Important Files Changed

Filename Overview
api/artifact_references.py New module providing derivation, sanitization, ownership validation, and budget-capping for workspace-file artifact references; well-bounded with path safety, traversal rejection, and circular-reference protection in the custom JSON size estimator.
api/streaming.py Adds _anchor_artifact_events accumulation, emit of artifact_reference SSE events after proven mutations, and terminal-state reconciliation into the anchor scene; _reconcile_stream_artifacts_into_terminal_anchor_scene omits owner_authority: server but this is self-healing on the browser's first settlement call.
api/routes.py Extends _handle_session_anchor_scene to merge existing and incoming scenes with ownership validation, sets owner_authority: server on records with trusted identity, and wires _anchor_run_id into hydration; logic is correct and well-tested.
static/messages.js Adds artifact_reference SSE event listener (render-skipped), scene artifact bounding before persistence, and artifact hydration in _hydrateAnchorRegistryFromActivityScene; bounding/dedup helpers are near-identical to those added in assistant_turn_anchors.js.
static/assistant_turn_anchors.js Replaces unbounded anchor.artifacts.push() with a bounded append helper and applies the same budget cap on scene export; dedup key and bounding logic is duplicated from messages.js.
static/sessions.js Widens hasAnchorActivityScene to treat artifact-only or side-effect-only scenes as valid, ensuring artifact-only scenes trigger reconnection hydration.
tests/test_live_anchor_artifact_reference.py New test file covering producer, path-safety, ordering, replay-cursor, cancellation, and frontend routing for the artifact reference pipeline.
tests/test_anchor_scene_persistence.py Extended with artifact budget trimming, foreign-owner rejection, ownerless bootstrap rejection, stable-run stream-rotation acceptance, and malformed-parent-owner cases; thorough coverage of the new ownership contract.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Agent
    participant StreamingPy as streaming.py
    participant ArtifactRefs as artifact_references.py
    participant RunJournal as Run Journal
    participant Browser
    participant RoutesPy as routes.py

    Agent->>StreamingPy: tool.completed (write_file / patch)
    StreamingPy->>ArtifactRefs: derive_file_artifact_references(name, args, result, workspace)
    ArtifactRefs-->>StreamingPy: "[{kind, path, source_tool, tool_call_id}]"
    StreamingPy->>RunJournal: "put('artifact_reference', payload) -> event_id"
    StreamingPy->>StreamingPy: _record_anchor_artifact_reference(payload, event_id)
    RunJournal-->>Browser: "SSE: artifact_reference {path, ...}"
    Browser->>Browser: _applyToAnchor('artifact_reference', d, render:false)

    Note over StreamingPy: On terminal state (completed / cancelled / error)
    StreamingPy->>StreamingPy: _reconcile_stream_artifacts_into_terminal_anchor_scene()
    StreamingPy->>ArtifactRefs: merge_anchor_activity_scene(existing, incoming, ...)
    StreamingPy->>StreamingPy: "session.anchor_activity_scenes[key] = record"
    StreamingPy->>StreamingPy: session.save()

    Note over Browser: Scene settlement
    Browser->>RoutesPy: POST /api/session/anchor-scene
    RoutesPy->>ArtifactRefs: "merge_anchor_activity_scene(existing, incoming, reject_owner_mismatch=True)"
    ArtifactRefs-->>RoutesPy: merged scene
    RoutesPy->>RoutesPy: "record[owner_authority] = server"
    RoutesPy->>RoutesPy: session.save()
    RoutesPy-->>Browser: 200 OK
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Agent
    participant StreamingPy as streaming.py
    participant ArtifactRefs as artifact_references.py
    participant RunJournal as Run Journal
    participant Browser
    participant RoutesPy as routes.py

    Agent->>StreamingPy: tool.completed (write_file / patch)
    StreamingPy->>ArtifactRefs: derive_file_artifact_references(name, args, result, workspace)
    ArtifactRefs-->>StreamingPy: "[{kind, path, source_tool, tool_call_id}]"
    StreamingPy->>RunJournal: "put('artifact_reference', payload) -> event_id"
    StreamingPy->>StreamingPy: _record_anchor_artifact_reference(payload, event_id)
    RunJournal-->>Browser: "SSE: artifact_reference {path, ...}"
    Browser->>Browser: _applyToAnchor('artifact_reference', d, render:false)

    Note over StreamingPy: On terminal state (completed / cancelled / error)
    StreamingPy->>StreamingPy: _reconcile_stream_artifacts_into_terminal_anchor_scene()
    StreamingPy->>ArtifactRefs: merge_anchor_activity_scene(existing, incoming, ...)
    StreamingPy->>StreamingPy: "session.anchor_activity_scenes[key] = record"
    StreamingPy->>StreamingPy: session.save()

    Note over Browser: Scene settlement
    Browser->>RoutesPy: POST /api/session/anchor-scene
    RoutesPy->>ArtifactRefs: "merge_anchor_activity_scene(existing, incoming, reject_owner_mismatch=True)"
    ArtifactRefs-->>RoutesPy: merged scene
    RoutesPy->>RoutesPy: "record[owner_authority] = server"
    RoutesPy->>RoutesPy: session.save()
    RoutesPy-->>Browser: 200 OK
Loading

Reviews (52): Last reviewed commit: "Merge remote-tracking branch 'origin/mas..." | Re-trigger Greptile

Comment thread api/streaming.py Outdated
Comment thread api/artifact_references.py Outdated
Comment on lines +13 to +16

def _function_block(source: str, name: str) -> str:
start = source.index(f"def {name}(")
next_def = source.find("\n def ", start + 1)

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.

P2 _function_block is fragile against streaming.py restructuring

The helper locates function boundaries by searching for the literal string "\n def " (12 spaces). If any sibling nested function is added between on_tool and on_tool_start, or if indentation changes, the extracted block will silently shrink — potentially no longer containing put('artifact_reference', ...) — and block.index(...) will raise ValueError with a misleading message rather than a meaningful assertion failure. A small comment explaining the indentation expectation, or using a marker comment in streaming.py as a sentinel, would make future breakages easier to diagnose.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare follow-up pushed in b5f4f77:

  • Refreshed the branch onto current master (b470793).
  • Narrowed the generic build/dist artifact filter to root-level generated trees, while keeping VCS/cache/dependency directories rejected by path component. Added a regression so docs/build/report.md remains a valid workspace artifact but root build output is still excluded.
  • Left the legacy on_tool artifact event without tool_call_id intentionally: Hermes Agent's legacy tool_progress_callback carries result/duration/error but no stable call id. The modern structured on_tool_complete path still attaches tool_call_id; synthesizing one for legacy would not bind to a real tool_complete id.

Local verification for the follow-up:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py (7 passed)
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
  • git diff --check

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in e1cf64a:

  • Merged current origin/master at ab937ef (release PR Release: msg_limit ceiling metadata decoupling (#6214, @webtecnica) #6216) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • Local verification after the merge:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py (7 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
    • git diff --check

GitHub Actions and Greptile have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in 3c4842e:

  • Merged current origin/master at 52c962c (release PR Release: Transparent Stream multi-segment prefix dedupe (#6189, @ai-ag2026) #6217) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • Because the incoming base touched static/messages.js, I verified both this PR's artifact-reference coverage and the incoming transparent-stream prefix-dedupe coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_5749_anchor_scene_client_prefix_dedupe.py tests/test_issue5749_transparent_stream_prefix_dedupe.py (18 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
    • git diff --check

GitHub checks have restarted on the new head; no Frank action is needed.

@nesquena-hermes nesquena-hermes added the size:L Large PR (>10 files or >250 LOC) label Jul 18, 2026
@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in c6dffcb:

  • Merged current origin/master at 02aa91b (release PR Release: stop false Compressing-context card (#6184, @carlotestor) #6223) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • The incoming base touched api/streaming.py and static/messages.js, so I verified this PR's artifact-reference coverage plus the incoming auto-compression card coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_auto_compression_card.py (74 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
    • git diff --check

GitHub checks and Greptile have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in 180dc6a:

  • Merged current origin/master at 735ba1c (release PR Release: extension session-open handler + renderTranscript API (#5508, @ChonSong) #6226) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • The incoming base touched static/boot.js, static/sessions.js, and extension-session tests, so I verified this PR's artifact-reference coverage plus the incoming extension/session coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_extension_session_hooks.py tests/test_issue1611_session_profile_filtering.py (50 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
    • git diff --check

GitHub checks and Greptile have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Proof-matrix aftercare at d2364c75a: added the explicit foreign-drive rejection case required by #6205 (C:\\... must not be treated as workspace-relative on POSIX). Focused gate: 24 passed, including success/failure, containment, symlink escape, ordering, cancellation journalability, and frontend routing. This follow-up is test-only; production behavior is unchanged.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in 388575f:

  • Merged current origin/master at 92a7189 (release PR Release: durable run-journal recovery + full tool args (#6197, @franksong2702) #6236) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • The incoming base touched run-journal recovery / full tool args paths and auto-merged static/messages.js, so I verified this PR's artifact-reference coverage plus neighboring run-journal and gateway stream recovery coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_inflight_stream_reuse.py tests/test_run_journal_routes.py tests/test_webui_gateway_chat_backend.py tests/test_cross_session_message_load_isolation.py (111 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/run_journal.py api/routes.py api/gateway_chat.py
    • git diff --check

GitHub checks have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in a86ce5b:

  • Merged current origin/master at a4a2f49 (release PR Release: folder-download subpath baseURI fix (#6227, @steezypunk) #6237) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • The incoming base only touched CHANGELOG.md and static/ui.js for the folder-download subpath baseURI fix, so I verified this PR's artifact-reference coverage plus the affected folder-download coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_folder_download.py (19 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py
    • git diff --check

GitHub checks have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Base refresh pushed in 009e517:

  • Merged current origin/master at d195b09 (release PR Release: GLM per-version reasoning controls (#6219, @rh-id) #6243) into Frank's PR branch; merge was clean with no manual conflict resolution.
  • The incoming base touched GLM per-version reasoning controls in api/config.py, static/index.html, static/ui.js, and reasoning tests, so I verified this PR's artifact-reference coverage plus the affected reasoning coverage:
    • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_reasoning_chip_js_behaviour.py tests/test_reasoning_show_hide.py tests/test_zai_reasoning_effort_gating.py (138 passed)
    • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/config.py
    • git diff --check

GitHub checks and Greptile have restarted on the new head; no Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in eb746c5fd.

3d32cb00d was fully green, including Greptile, but GitHub marked the PR behind base again. I merged current origin/master c414c39f1 into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was extension sidecar-auth release work and touched api/extension_sidecar_auth.py, api/extensions.py, api/routes.py, docs/EXTENSIONS.md, static/panels.js, static/style.css, and tests/test_extension_sidecar_proxy.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_extension_sidecar_proxy.py -> 94 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-c414c39f1 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/routes.py api/streaming.py api/extensions.py api/extension_sidecar_auth.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_extension_sidecar_proxy.py
  • node --check static/messages.js
  • node --check static/sessions.js
  • node --check static/assistant_turn_anchors.js
  • node --check static/panels.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Readback after push: head eb746c5fd8fcf64bcd9707f4e7d4926873e43fb3, base c414c39f1, MERGEABLE; Actions and Greptile restarted on the refreshed head.

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @franksong2702 — big progress: an adversarial re-gate confirms all three prior blockers are fixed (exact-path artifact ownership with boundary-whitespace rejected + decoy-filename regression passing; lifecycle ownership now projected at api/routes.py:3286/3747, reconciled for terminal success/error/cancel at api/streaming.py:1668, and hydrated on reload at static/messages.js:4041; result/patch/path-count/total-byte limits at api/artifact_references.py:18-26 with derivation isolated after tool_complete). Projection stays separate from activity rows — no dup/reorder/final-answer-ownership change. Full suite 13531/0.

Two remaining issues before it can land (both verified):

1. [SILENT] tool-call-ID cap bypassed by normalization — api/artifact_references.py:198

Same class as the original path finding, now on the tool-call-ID field: the code .strip()s before measuring, so a 300,007-byte ID (300,000 spaces + call-ok) is accepted and stored as call-ok. That's unbounded preprocessing and it attributes the artifact to a different identity than the caller sent. Fix: require a string, measure the exact original value first, reject boundary whitespace and oversized values, and preserve the accepted ID unchanged (mirror exactly what you did for the path field at :141).

2. Stream-wide budget enforced too late — api/streaming.py:8361,8472

The count/byte budget is checked at final scene assembly, but each artifact event is put() into the journal/queue before that check, so a flood can still hit the journal/queue path. Fix: enforce the stream-wide artifact count + byte budget before journaling/queueing each event, and add a test that asserts the queue/journal count is bounded (not only the final scene).

(Also: rebase onto current master — it now includes the token-v1 sidecar-auth merge c414c39f1 (#6331) — before the next gate.)

Once the tool-call-ID is validated exact-and-bounded and the budget is enforced pre-journal, I'll re-run the full gate. The rest of the diff is solid. 🙏

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare follow-up pushed in c96859ff6 for the latest review.

Addressed the two remaining blockers:

  • api/artifact_references.py: tool_call_id is now exact-and-bounded like paths. Explicit IDs must already be strings, non-empty, within the byte cap, and free of boundary whitespace before they are accepted; accepted IDs are preserved unchanged.
  • api/streaming.py: derived artifact events now pass the stream-wide bounded artifact check before put('artifact_reference', ...), so floods are capped before the run journal and SSE queue, not only at terminal scene assembly.
  • tests/test_live_anchor_artifact_reference.py: added the 300,000-space tool_call_id regression plus boundary-whitespace/non-string negatives, and extended the real repeated tool-complete ingress test to assert journal and queue artifact counts stay bounded with the final scene.

Verification passed:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py -> 19 passed
  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_live_anchor_stable_run_identity.py tests/test_stable_assistant_turn_anchor_registry.py tests/test_issue3820_chat_activity_display_mode.py tests/test_live_to_final_anchor_visible_order.py -> 207 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-tool-call-budget .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py tests/test_live_anchor_artifact_reference.py
  • node --check static/messages.js
  • node --check static/sessions.js
  • node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Readback after push: head c96859ff6d62297f723fc954198bbc7f0a0659dc, base c414c39f1, MERGEABLE; Actions and Greptile restarted on the new head.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in d27b0feb1.

c96859ff6 was fully green, including Greptile, but GitHub marked the PR behind base. I merged current origin/master 302843c89 into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6371 for compact completed image tool-results. It touched CHANGELOG.md, api/routes.py, api/streaming.py, tests/test_context_history_sanitization.py, tests/test_issue1217_transcript_compaction.py, and tests/test_run_journal_streaming_static.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_context_history_sanitization.py tests/test_issue1217_transcript_compaction.py tests/test_run_journal_streaming_static.py -> 120 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-302843c89 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/routes.py api/streaming.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_context_history_sanitization.py tests/test_issue1217_transcript_compaction.py tests/test_run_journal_streaming_static.py
  • node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Readback after push: head d27b0feb1b85a0b435940b1b5903fcaa9e8b2d66, base 302843c89; Actions and Greptile restarted on the refreshed head. No Frank action is needed.

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @franksong2702 — both remaining blockers from the last round are confirmed fixed: the tool-call-ID is now measured exact-and-bounded with boundary whitespace rejected, and the artifact budget is enforced before put()/journaling at both emission sites (api/streaming.py:7621-7634, 8453-8457, 8566-8570). The 3 original findings all remain closed (exact-path ownership, lifecycle projection/hydration across replay/reload/settle/error/cancel, derivation isolated after tool_complete), no dup/reorder/final-answer-ownership change. Full suite 13548/0.

An adversarial re-gate found 3 remaining edge cases on the same bounded-parsing subsystem — the middle one is a real bypass, so one more pass:

1. [SILENT] _MAX_RESULT_BYTES not enforced for dict results — api/artifact_references.py:80-91

The byte cap is applied for string results but a dictionary result skips it: a verified 65,537-byte structured result still produced an artifact reference. Fix: use an early-exit bounded JSON-size walker for dict results and reject over-limit or unserializable results before reading mutation fields.

2. [minor] .strip() runs before the size rejection — api/artifact_references.py:54-63,199-205

The constant-time char-count rejection should happen on the exact untrimmed value before calling .strip(), so a huge input can't force unbounded preprocessing. (Same shape as the tool-call-ID fix — apply it to the other measured fields.)

3. [minor] Aggregate byte accounting omits JSON framing — api/artifact_references.py:513-523

The 32,768-byte cap doesn't count list brackets/commas, so a probe produced 32,769 bytes. Fix: include the complete serialized-list framing in the counter, and mirror it browser-side at static/assistant_turn_anchors.js:211-214 and static/messages.js:3669-3670.

Once dict results are byte-capped (the material one), the size check precedes .strip(), and the framing bytes are counted on both sides, I'll re-run the full gate. This is close — the whole ownership/lifecycle/timing core is solid now. 🙏

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare follow-up pushed in 9dde8066c.

This addresses the 23:27Z bounded-parsing review edge cases:

  • api/artifact_references.py: dict tool results are now checked with an incremental bounded JSON encoder before any mutation fields are read; oversize or unserializable structured results fail closed.
  • api/artifact_references.py: measured string fields now check type, emptiness, and exact byte size before any boundary-whitespace trim comparison; accepted IDs/owner fields remain unchanged.
  • api/artifact_references.py, static/assistant_turn_anchors.js, and static/messages.js: artifact budget accounting now includes complete serialized-list framing ([] and commas), so server, Anchor registry, and browser persistence agree on the 32 KiB cap.
  • tests/test_live_anchor_artifact_reference.py: added regressions for oversize/unserializable dict results, exact string budget order, serialized-list framing, and browser Anchor framing parity.

Verification passed:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py -> 22 passed
  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_live_anchor_stable_run_identity.py tests/test_stable_assistant_turn_anchor_registry.py tests/test_issue3820_chat_activity_display_mode.py tests/test_live_to_final_anchor_visible_order.py -> 210 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-bounded-parsing .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py tests/test_live_anchor_artifact_reference.py
  • node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

GitHub checks have restarted on the new head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare CI retry pushed in 7a657a6e7.

The only failed check on 9dde8066c was the informational live-to-final (terminal-error) gate. The job log showed the known terminal-error lifecycle symptom: no /api/session/anchor-scene POST was observed before reload, with message summary showing no persisted Anchor scene.

I reran the exact gate locally on this checkout and it passed:

LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-9dde8066-recheck .venv/bin/python tests/browser_conversation_lifecycle.py

Result: live activity, terminal settlement, and hard-reload Anchor scene parity all passed.

I tried gh run rerun 29788077911 --failed, but GitHub denied it because this token lacks repo admin rights, so I pushed an empty retry commit. No product files changed in this retry.

Current head is 7a657a6e7; checks have restarted. No Frank action is needed.

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @franksong2702. I re-gated exact head 7a657a6e77f9 against the three findings from the review at d27b0feb1b85. The list-framing fix is correct across the server and both browser bounders, but the other two requested bounded-processing invariants remain incomplete.

This was a static-only re-gate. The mandatory threat scan returned SUSPICIOUS on the added local JS test harness's eval(src), so policy required NO-RUN. I did not execute the PR or its tests. The scanner hit looks consistent with the existing checked-in-module test idiom, but the fail-closed verdict remains authoritative.

1. The dict-result byte gate still performs unbounded scalar encoding

api/artifact_references.py::_json_within_bytes (:66-76) uses JSONEncoder.iterencode(). For a string value, CPython's encoder calls and yields _encoder(value) as one complete chunk before this loop can compare total with _MAX_RESULT_BYTES; _utf8_size(chunk) then creates another full representation. An arbitrarily large scalar is therefore fully processed before the 64 KiB rejection. This is not the requested early-exit bounded JSON-size walker.

The handler also catches TypeError, ValueError, and OverflowError, but not RecursionError, so a deeply nested structured result can escape instead of failing closed. The new test covers an ordinary 65 KiB string and a TypeError from object(), but neither boundary.

Fix: replace _json_within_bytes with a genuinely bounded, non-recursive compact-JSON size walker. Use an explicit container stack and active-ID set, count full compact JSON framing, reject cycles/unsupported values/excessive nesting, and stop before fully encoding any scalar once the cap cannot be met. Keep this gate before _mutation_landed() reads mutation fields.

2. Exact strings are measured before .strip(), but the constant-time precheck is still missing

_bounded_clean_string() (:54-63) and the tool-call-ID guard (:212-219) now measure the exact value before boundary-whitespace rejection, which fixes the normalization-order bug. Both still call _utf8_size(value) before a constant-time character-count rejection, however. A huge input must be UTF-8 encoded in full before it is rejected. The new 300,000-space assertion proves the returned value is []; it does not prove bounded preprocessing.

Fix: add len(value) > limit before _utf8_size(value) in both paths, then retain exact untrimmed UTF-8 measurement and boundary-whitespace rejection. Add a regression that makes any call to _utf8_size fail for an input whose character count already exceeds the byte cap, plus deep-nesting/cycle cases for the dict gate.

The framing work in bound_anchor_artifact_events, _boundedAnchorArtifactEvents, and _boundedAnchorSceneArtifacts can remain as-is. After these two bounded-processing gaps are closed and the threat scan permits execution, the targeted artifact/anchor suites need a sandboxed run through warmup-safe-test.sh.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare follow-up pushed in 86f3d5dc0.

This addresses the 00:54Z bounded-processing review:

  • api/artifact_references.py: replaced the JSONEncoder.iterencode() gate with a non-recursive compact-JSON size walker. It uses an explicit stack, counts container/list/dict/string framing, tracks active container IDs to reject cycles, caps nesting, rejects unsupported values and non-string dict keys, and fails closed before mutation fields are read.
  • api/artifact_references.py: added len(value) > limit prechecks before _utf8_size() in _bounded_clean_string() and the explicit tool_call_id guard, while preserving exact untrimmed UTF-8 measurement and boundary-whitespace rejection for accepted strings.
  • tests/test_live_anchor_artifact_reference.py: added regressions proving oversized character-count inputs are rejected without calling _utf8_size(), and that cyclic / excessively deep structured results fail closed.
  • tests/test_live_anchor_artifact_reference.py: removed the newly added eval(src) JS harness by loading static/assistant_turn_anchors.js directly through require(). The remaining eval(extractFunc(...)) use is the pre-existing local function-extraction test idiom.

Verification passed:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py -> 24 passed
  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_live_anchor_stable_run_identity.py tests/test_stable_assistant_turn_anchor_registry.py tests/test_issue3820_chat_activity_display_mode.py tests/test_live_to_final_anchor_visible_order.py -> 212 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-bounded-walker .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py tests/test_live_anchor_artifact_reference.py
  • node --check static/messages.js
  • node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

I could not run warmup-safe-test.sh because that helper is not present in this checkout or the adjacent /Users/xuefusong tree; I used the repo-standard ./scripts/test.sh gates required by AGENTS.md.

Readback before this comment: PR head 86f3d5dc080299e69a2385c060f6fdce982c2e6e, base 302843c89f22d1ce2ad10619acd33382c9f48111, MERGEABLE; Actions and Greptile are running on the new head. No Frank action is needed.

@nesquena-hermes nesquena-hermes left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks @franksong2702. I re-gated exact head 86f3d5dc0802 against the 00:54Z bounded-processing findings. The prior three items now converge: scalar JSON strings and bounded string/tool-call-ID fields get a constant-time character lower-bound before UTF-8 work, and the recursive encoder path has been replaced with an explicit depth/cycle-aware walker. The mandatory threat scan is CLEAN, and the targeted sandbox gate passes all 24 tests in test_live_anchor_artifact_reference.py.

The replacement still has two deterministic bounded-work gaps, so this is not ready yet.

1. Wide containers are expanded before the 64 KiB cap can reject them

In api/artifact_references.py:_json_within_bytes, dict handling does items = list(item.items()) and then pushes every key/value pair onto stack; list/tuple handling likewise pushes every element before any child bytes are charged. An arbitrarily wide result therefore causes work and temporary memory proportional to the raw container width, independent of _MAX_RESULT_BYTES. A nested wide container has the same problem. This regresses the old encoder's lazy container iteration and defeats the bounded-work purpose of this gate.

Fix: use lazy iterator frames that schedule one dict pair or sequence element at a time. Keep the container ID active until its iterator exits, charge punctuation as each item is scheduled, and bound stack/work by nesting depth plus the serialized prefix examined, not raw width.

2. Serialized string results still encode before a constant-time lower-bound

_result_object calls _utf8_size(result) directly for string results. A very large serialized result is therefore fully encoded before the 64 KiB rejection. Add len(result) > _MAX_RESULT_BYTES before _utf8_size(result), as the new code now does for scalar strings and tool-call IDs.

Please add fail-first coverage for both shapes: instrumented very-wide dict/list inputs that prove scanning stops after a cap-derived prefix, plus an oversized direct serialized-result string whose _utf8_size call must never receive the over-limit value. The current 24 tests do not exercise either path; the nested scalar spy also does not fail on the old JSON-quoted encoder chunk.

The ownership, lifecycle, framing, and earlier string/depth fixes remain intact. This request is limited to closing the two bounded-work gaps introduced or retained in the latest scanner.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Handled the bounded-work follow-up from the 01:34Z review.

Pushed a3a0d7775 (fix: lazily bound artifact result scanning) to Frank's fork branch with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Changed:

  • api/artifact_references.py: _json_within_bytes now advances dict/list containers with lazy iterator frames, keeps container IDs active until iterator exit, and bounds work by nesting depth plus serialized prefix instead of raw width. Direct serialized string results now use a len(result) precheck before UTF-8 sizing.
  • tests/test_live_anchor_artifact_reference.py: added regressions for over-limit serialized result strings that must not reach _utf8_size, plus instrumented wide dict/list inputs proving scanner traversal stops at the cap-derived prefix and does not index every sequence element.

Verification passed:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py -> 25 passed
  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_live_anchor_stable_run_identity.py tests/test_stable_assistant_turn_anchor_registry.py tests/test_issue3820_chat_activity_display_mode.py tests/test_live_to_final_anchor_visible_order.py -> 213 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-bounded .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py tests/test_live_anchor_artifact_reference.py
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

No Frank action needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in dc3deae4f.

a3a0d7775 was fully green, including Greptile, but GitHub marked the PR behind base. I merged current origin/master a725a0f71 into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6375 and touched CHANGELOG.md, README.md, api/config.py, static/boot.js, and tests/test_issue4765_sessions_lru_eviction.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue4765_sessions_lru_eviction.py -> 88 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-a725a0f .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py api/config.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue4765_sessions_lru_eviction.py
  • node --check static/boot.js && node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Checks have restarted on the refreshed head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in ecd23ec59.

dc3deae4f was fully green, including Greptile, but GitHub marked the PR behind base again. I merged current origin/master 0deef392f into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6376 and touched CHANGELOG.md, static/panels.js, static/style.css, and tests/test_kanban_board_ui.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_kanban_board_ui.py -> 75 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-0deef392 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_kanban_board_ui.py
  • node --check static/panels.js && node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Checks have restarted on the refreshed head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in e32722ca2.

ecd23ec59 was fully green, including Greptile, but GitHub marked the PR behind base again. I merged current origin/master 48b40d930 into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6379 and touched CHANGELOG.md, api/profiles.py, api/streaming.py, tests/test_issue2023_hermes_home_skill_modules.py, tests/test_issue2848_checkpoint_profile.py, tests/test_issue5567_profile_home_override.py, tests/test_sprint46.py, tests/test_title_aux_routing.py, and tests/test_update_banner_fixes.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue2023_hermes_home_skill_modules.py tests/test_issue2848_checkpoint_profile.py tests/test_issue5567_profile_home_override.py tests/test_sprint46.py tests/test_title_aux_routing.py tests/test_update_banner_fixes.py -> 259 passed, 1 skipped
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-48b40d930 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py api/profiles.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue2023_hermes_home_skill_modules.py tests/test_issue2848_checkpoint_profile.py tests/test_issue5567_profile_home_override.py tests/test_sprint46.py tests/test_title_aux_routing.py tests/test_update_banner_fixes.py
  • node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Checks have restarted on the refreshed head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in 1ec814432.

e32722ca2 was fully green, including Greptile, but GitHub marked the PR behind base again. I merged current origin/master 6b9b9dc80 into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6384 and touched CHANGELOG.md, static/style.css, and tests/test_kanban_ui_static.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_kanban_ui_static.py -> 120 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-6b9b9dc80 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_kanban_ui_static.py
  • node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Checks have restarted on the refreshed head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Aftercare base refresh pushed in 5566e2764.

1ec814432 was fully green, including Greptile, but GitHub marked the PR behind base again. I merged current origin/master 4f063d16f into Frank's branch and pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base was release PR #6386 and touched CHANGELOG.md, static/i18n.js, static/style.css, static/workspace.js, locale tests, and tests/test_issue6067_artifact_paths.py.

Verification passed after the merge:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue6067_artifact_paths.py tests/test_chinese_locale.py tests/test_czech_locale.py tests/test_japanese_locale.py tests/test_korean_locale.py tests/test_polish_locale.py tests/test_russian_locale.py tests/test_spanish_locale.py tests/test_turkish_locale.py tests/test_vietnamese_locale.py tests/test_zh_hant_locale.py -> 137 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-4f063d16 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue6067_artifact_paths.py tests/test_chinese_locale.py tests/test_czech_locale.py tests/test_japanese_locale.py tests/test_korean_locale.py tests/test_polish_locale.py tests/test_russian_locale.py tests/test_spanish_locale.py tests/test_turkish_locale.py tests/test_vietnamese_locale.py tests/test_zh_hant_locale.py
  • node --check static/i18n.js && node --check static/workspace.js && node --check static/messages.js && node --check static/sessions.js && node --check static/assistant_turn_anchors.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

Checks have restarted on the refreshed head. No Frank action is needed.

@franksong2702

Copy link
Copy Markdown
Contributor Author

Handled PR #6207 base-refresh aftercare.

5566e2764 was fully green, including Greptile, but GitHub marked the PR BEHIND again. I merged current origin/master c0d9d815127c456764be9fdc3fe7aa8bed913dd2 into Frank’s branch, producing merge commit 5d9ae42ef6830f3726641500e0d18f024241a1e5, then pushed with:

git push fork-pr HEAD:refs/heads/franksong2702/live-stream-artifact-reference-derivation

Incoming base touched CHANGELOG.md, api/config.py, static/boot.js, static/i18n.js, static/index.html, static/panels.js, static/style.css, and tests/test_issue6160_hide_empty_state_panel.py.

Verification passed:

  • ./scripts/test.sh tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue6160_hide_empty_state_panel.py -> 80 passed
  • LIFECYCLE_SCENARIO=terminal-error LIFECYCLE_ARTIFACT_DIR=/tmp/hermes-lifecycle-terminal-error-pr6207-c0d9d815 .venv/bin/python tests/browser_conversation_lifecycle.py -> passed
  • .venv/bin/python -m py_compile api/artifact_references.py api/streaming.py api/routes.py api/config.py tests/test_live_anchor_artifact_reference.py tests/test_anchor_scene_persistence.py tests/test_issue6160_hide_empty_state_panel.py
  • node --check static/boot.js, node --check static/i18n.js, node --check static/panels.js, node --check static/messages.js
  • python3 scripts/ruff_lint.py --diff origin/master
  • git diff --check

I’ll monitor the restarted checks/Greptile on the refreshed head. No Frank action needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested Maintainer left detailed feedback requesting changes; PR is waiting on author to address gate-fail Gate found blocking issue(s); fix-spec in comment; awaiting fix/re-push size:L Large PR (>10 files or >250 LOC)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Live Stream: derive turn-owned artifact references from successful file mutations

2 participants