Skip to content

feat(cli): report active Norn sessions to HerdR - #18

Draft
ijnebzor wants to merge 3 commits into
mainfrom
ijneb/herdr-active-norn-sessions
Draft

feat(cli): report active Norn sessions to HerdR#18
ijnebzor wants to merge 3 commits into
mainfrom
ijneb/herdr-active-norn-sessions

Conversation

@ijnebzor

@ijnebzor ijnebzor commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

Report active Norn sessions to the surrounding HerdR pane so they appear in HerdR's Agents view with their real Norn session identity.

When Norn is launched inside a HerdR-managed pane, HerdR provides HERDR_ENV, HERDR_PANE_ID, and HERDR_SOCKET_PATH. This change uses HerdR's public pane socket contract to:

  • claim the pane with pane.report_agent
  • report agent = "norn" and state = "working"
  • include the root Norn session ID and durable JSONL path when available
  • release the claim with pane.release_agent when the TUI or print/driven run ends

The integration is best-effort: missing/invalid HerdR state, socket timeouts, or server rejection are logged and never fail the Norn run. Non-Unix builds retain a no-op implementation.

Why lifecycle reporting instead of detection alone?

HerdR can detect an executable/process name, and that may already make a pane look like it contains Norn. Active lifecycle reporting adds the information process detection cannot reliably infer: the canonical Norn session ID and session-store path. It also lets Norn release authority cleanly when it exits, so a surrounding/previous pane reporter can become authoritative again.

This intentionally covers active pane-backed sessions, not an archive of stopped sessions from ~/.norn/session-store.

Coverage

  • TUI execution path
  • print execution path
  • driven execution path
  • report/release request shape
  • monotonically increasing sequence values
  • HerdR server error handling

Verification

  • cargo test -p norn-cli
  • cargo fmt --all -- --check
  • cargo clippy -p norn-cli --all-targets -- -D warnings
  • live probe against HerdR 0.7.1 confirmed agent: "norn", agent_status: "working", and the reported session metadata in herdr agent list

Summary by CodeRabbit

  • New Features

    • Added HerdR pane integration for active Norn sessions.
    • Active sessions can report their agent and session information to HerdR.
    • Pane ownership is automatically released when a session ends, restoring the previous pane state.
  • Bug Fixes

    • HerdR connection or server errors no longer interrupt Norn sessions; they are handled gracefully with warnings.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ijnebzor, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ae44b47e-0053-47ae-b016-8e048b75f9e2

📥 Commits

Reviewing files that changed from the base of the PR and between 12dc1d5 and 1f17c6e.

📒 Files selected for processing (9)
  • crates/norn-cli/src/herdr.rs
  • crates/norn-cli/src/print/orchestrator.rs
  • crates/norn-cli/src/tui/driver.rs
  • crates/norn-tui/src/app/event_loop.rs
  • crates/norn-tui/src/app/mod.rs
  • crates/norn-tui/src/app/slash.rs
  • crates/norn-tui/src/app/turn/run.rs
  • crates/norn-tui/src/lib.rs
  • crates/norn-tui/tests/pty_smoke.rs
📝 Walkthrough

Walkthrough

Norn adds a Unix HerdR pane integration that reports active sessions, attaches optional session metadata, and releases the pane claim after print or TUI execution.

Changes

HerdR integration

Layer / File(s) Summary
HerdR protocol and claim lifecycle
crates/norn-cli/src/herdr.rs
Adds Unix socket requests for pane.report_agent and pane.release_agent, environment validation, timeout handling, sequence generation, drop-based release, non-Unix stubs, and protocol tests.
Session lifecycle wiring
crates/norn-cli/src/lib.rs, crates/norn-cli/src/print/orchestrator.rs, crates/norn-cli/src/tui/driver.rs
Registers the module, derives session paths, claims panes before execution, and releases claims after print or TUI session completion.

Estimated code review effort: 4 (Complex) | ~45 minutes

Suggested reviewers: tomwhiting

Sequence Diagram(s)

sequenceDiagram
  participant Norn
  participant PaneClaim
  participant HerdR
  Norn->>PaneClaim: claim(session_id, session_path)
  PaneClaim->>HerdR: pane.report_agent
  HerdR-->>PaneClaim: response
  Norn->>Norn: run print or TUI session
  Norn->>PaneClaim: drop claim
  PaneClaim->>HerdR: pane.release_agent
  HerdR-->>PaneClaim: response
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: reporting active Norn sessions to HerdR from the CLI.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ijneb/herdr-active-norn-sessions

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.

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 12dc1d5ac5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/norn-cli/src/herdr.rs Outdated
Comment on lines +194 to +198
LAST_SEQUENCE
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |previous| {
Some(wall_clock.max(previous.saturating_add(1)))
})
.unwrap_or_else(|previous| previous)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Return the updated HerdR sequence number

When HerdR is active, fetch_update returns the previous atomic value, not the value produced by the closure, so the first pane.report_agent in every Norn process is sent with seq = 0 while the atomic is updated to the wall-clock value. After a run releases with a large sequence, launching Norn again in the same pane resets this static and reports 0 again, which can make HerdR treat the new claim as stale; return the stored candidate value instead of the old value.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Confirmed. AtomicU64::fetch_update returns the previous value, so the first request could emit seq = 0. Fixed in 2c870b7 with a compare-exchange loop that returns the newly stored monotonic value; added a regression assertion that the first reported sequence is nonzero. Focused tests and strict clippy pass.

Comment thread crates/norn-cli/src/tui/driver.rs Outdated
None => data_dir.join(format!("{}.jsonl", entry.id)),
})
});
let herdr_claim = crate::herdr::PaneClaim::claim(&session_id, herdr_session_path.as_deref());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Refresh the HerdR claim after TUI session rotation

In an interactive HerdR pane, this claim is created once from the startup session_id, but the TUI's /new and /clear path creates a fresh persisted store and updates runtime.session_id in crates/norn-tui/src/app/slash.rs:272-278. After that rotation, HerdR continues to show the retired session id/path until exit even though new events are written under the new session; the claim needs to be renewed or updated when the TUI rotates sessions.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 1f17c6e. norn-tui now emits SessionChanged after /new or /clear commits rotation, including the new canonical session ID and JSONL path. The CLI-owned HerdR reporter updates its claim while preserving the current idle/working state.

Comment thread crates/norn-cli/src/herdr.rs Outdated
pane_id: &self.pane_id,
source: SOURCE,
agent: AGENT,
state: "working",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Report idle TUI sessions as idle

Because the TUI creates this claim before entering run_app and holds it until exit, an interactive Norn pane with no initial prompt—or one sitting between turns waiting for user input—is reported to HerdR as working indefinitely. HerdR uses this semantic state for agent rollups and waits, so idle interactive panes will look busy until the process exits; the TUI path needs to report idle outside active turns or update the state on turn boundaries.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Addressed in 1f17c6e. The TUI initially claims idle, emits TurnStarted/TurnFinished around the centralized root run_turn, and the CLI reporter maps those to HerdR working/idle. A Drop guard emits TurnFinished on error and cancellation paths as well.

@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: 2

🧹 Nitpick comments (2)
crates/norn-cli/src/herdr.rs (1)

106-184: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Errors are raw Strings instead of thiserror-typed errors.

from_env, report, release, and send all return Result<_, String>. Per coding guidelines, library code (outside the aion-server binary) should use thiserror for domain errors rather than ad-hoc strings.

As per coding guidelines, "Use thiserror for library domain errors and anyhow only in the aion-server binary for top-level error reporting."

🤖 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 `@crates/norn-cli/src/herdr.rs` around lines 106 - 184, Replace the raw String
errors in from_env, report, release, and send with a dedicated thiserror-derived
domain error type for HerdR operations. Add variants covering environment
validation, socket I/O, serialization/deserialization, and rejected responses,
then propagate errors with context while preserving the existing messages and
Result behavior.

Source: Coding guidelines

crates/norn-cli/src/print/orchestrator.rs (1)

356-374: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated session→JSONL-path derivation for HerdR reporting. Both call sites independently reimplement the same rel_path vs. format!("{}.jsonl", entry.id) fallback logic to compute the HerdR report path.

  • crates/norn-cli/src/print/orchestrator.rs#L356-L374: extract a shared helper (e.g. crate::herdr::session_report_path(entry, data_dir) -> PathBuf) and call it here instead of the inline match.
  • crates/norn-cli/src/tui/driver.rs#L192-L200: call the same shared helper instead of duplicating the match against persist_data_dir.
🤖 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 `@crates/norn-cli/src/print/orchestrator.rs` around lines 356 - 374, The
session-to-JSONL path derivation is duplicated across HerdR reporting call
sites. Add a shared `crate::herdr::session_report_path(entry, data_dir)` helper
encapsulating the `rel_path` versus `{entry.id}.jsonl` fallback, then use it in
`crates/norn-cli/src/print/orchestrator.rs` lines 356-374 and
`crates/norn-cli/src/tui/driver.rs` lines 192-200; preserve each caller’s
existing data-directory error handling.
🤖 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 `@crates/norn-cli/src/herdr.rs`:
- Around line 156-184: Update the HerdR request flow around send and its
claim/Drop callers so synchronous UnixStream connect, read, and write operations
cannot block async executor threads indefinitely. Move the complete blocking
send operation into tokio::task::spawn_blocking and apply an explicit timeout to
the spawned operation, or replace it with tokio::net::UnixStream using bounded
connect/read/write operations; preserve the existing error messages and response
validation.
- Around line 187-199: The next_sequence function currently returns the
pre-update value from LAST_SEQUENCE.fetch_update, producing stale duplicate
sequence numbers. Capture the value computed by the update closure and return
that written successor, while preserving the wall-clock and monotonic-increment
logic; update tests to verify distinct emitted sequences rather than relying
only on monotonic assertions.

---

Nitpick comments:
In `@crates/norn-cli/src/herdr.rs`:
- Around line 106-184: Replace the raw String errors in from_env, report,
release, and send with a dedicated thiserror-derived domain error type for HerdR
operations. Add variants covering environment validation, socket I/O,
serialization/deserialization, and rejected responses, then propagate errors
with context while preserving the existing messages and Result behavior.

In `@crates/norn-cli/src/print/orchestrator.rs`:
- Around line 356-374: The session-to-JSONL path derivation is duplicated across
HerdR reporting call sites. Add a shared
`crate::herdr::session_report_path(entry, data_dir)` helper encapsulating the
`rel_path` versus `{entry.id}.jsonl` fallback, then use it in
`crates/norn-cli/src/print/orchestrator.rs` lines 356-374 and
`crates/norn-cli/src/tui/driver.rs` lines 192-200; preserve each caller’s
existing data-directory error handling.
🪄 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: c29870d4-99d3-4918-ac7b-e1de87a57e84

📥 Commits

Reviewing files that changed from the base of the PR and between 85a873b and 12dc1d5.

📒 Files selected for processing (4)
  • crates/norn-cli/src/herdr.rs
  • crates/norn-cli/src/lib.rs
  • crates/norn-cli/src/print/orchestrator.rs
  • crates/norn-cli/src/tui/driver.rs

Comment thread crates/norn-cli/src/herdr.rs
Comment thread crates/norn-cli/src/herdr.rs
@ijnebzor

Copy link
Copy Markdown
Collaborator Author

Pre-merge lifecycle review

I did a second adversarial pass after the automated reviews. One confirmed protocol bug is now fixed in 2c870b7:

  • fetch_update returned the previous atomic value, so the first report could send seq = 0 and a later process in the same pane could be treated as stale. The new compare-exchange loop returns the stored monotonic value, with regression coverage.

I recommend holding merge until we choose how to handle the TUI-specific findings below, or narrowing this PR to print/driven mode:

  1. TUI idle state is currently inaccurate. The claim is created before run_app with state = "working" and held until exit. A TUI waiting for input therefore remains authoritative and “working” in HerdR indefinitely. HerdR uses this state for rollups/waits, so the TUI path should update on turn boundaries (idle ↔ working), not claim working for the process lifetime.

  2. /new and /clear rotate the TUI session after the claim is created. runtime.session_id and the persisted store are replaced, but HerdR continues showing the startup session ID/path. The claim needs an update/re-claim hook when rotate_store_dependents completes.

  3. Socket connect is synchronous and not bounded. Read/write have 500 ms timeouts, but UnixStream::connect itself is synchronous and can block the async executor thread before those timeouts apply. The complete exchange should move off the executor (spawn_blocking/dedicated worker) or use bounded async Unix socket I/O. This matters on both claim and release.

Non-blocking cleanup:

  • A typed thiserror error enum would match repository conventions better than raw String errors, but this is maintainability rather than a behavioral blocker.
  • Current protocol tests are good for request shape/rejection/sequence ordering; once TUI lifecycle updates exist, they should also cover idle/working transitions and session rotation.

My preferred shape is to keep print/driven reporting as-is, then give the TUI a small lifecycle reporter subscribed to root turn/session-rotation events. That preserves authoritative HerdR semantics instead of competing with HerdR’s process detector using a permanently-working claim.

@Deanofish Deanofish 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.

Reviewed at head 2c870b7 with fresh eyes on both sides of the contract — I administer HerdR-managed agents daily, and I cross-checked every wire-level claim against HerdR 0.7.1's CLI contract and (via an independent first-pass) its source. The core design is right: RAII claim/release, per-request connections, best-effort discipline, field-for-field contract match on the request shapes (verified against herdr pane report-agent/release-agent), and correct explicit-drop placement on both wirings. The findings below are why I'm requesting changes rather than approving: three of them break the PR's own two promises — "never affects the Norn run" and "HerdR shows the real session identity" — on reachable paths.

Major

1. The socket exchange is not actually bounded — a wedged HerdR can hang the run.
send() (herdr.rs:156) installs read/write timeouts only after UnixStream::connect, which has no timeout: a listening-but-wedged server with a full accept backlog blocks indefinitely. The read side is also per-recv, not per-exchange — read_line resets the 500 ms clock on every byte received, so a peer trickling bytes without a newline keeps the call (and the unbounded String) alive forever. Both claim() and Drop call this synchronously from async paths, so the block lands on a runtime thread at startup or exit. Fix shape: one absolute deadline across connect+write+read (nonblocking connect + poll, or socket2), plus a max response frame size. This is the one place the PR's "integration failures are observational and never fail the run" invariant can be violated.

2. TUI /new and /clear leave HerdR advertising the retired session.
The claim is constructed once at startup with that session's id and JSONL path (driver.rs:200) and never touched again. Session rotation (norn-tui/src/app/slash.rsruntime.session_id = Some(new_id)) has no PaneClaim involvement, verified by grep: the two startup claim sites are the only uses. After a rotation, a long-lived TUI process reports the id/path of the conversation the user explicitly left — an Agents-view consumer resuming from that metadata opens the wrong session. Needs a PaneClaim::update(...) (re-report with new identity, same source) invoked at the rotation commit point; print-mode /clear has the same gap.

3. Catchable-signal termination never releases — the pane stays "working" forever.
Cleanup exists only in Drop. There is no SIGINT/SIGTERM handling anywhere in norn-cli/norn-tui (verified repo-wide); print-mode Ctrl-C and any SIGTERM take the OS default path, which runs no destructors. HerdR retains reported authority until released or superseded — no TTL — so an interrupted print/driven run leaves a dead session displayed as norn/working with a stale id and path, and the pane's previous reporter is never restored. Since restoring the previous reporter is one of this PR's stated goals, catchable signals need a bounded release on the way out (SIGKILL is a fair documented limitation).

4. One fixed authority key across processes: a nested Norn releases its parent's claim.
Every process claims and releases as ("herdr:norn", "norn") with no claim identity in ReleaseParams. HerdR keys sequence acceptance by source and checks release by source+agent only — so a nested/overlapping norn --print in the same pane (inherits the env) claims over the parent, and whichever process exits first clears the survivor's authority. Also: sequences are wall-clock-seeded and HerdR silently ignores seq ≤ last-accepted, so after a clock step-back a fresh process's reports can be dropped while claim() still returns Some — claim/release pairing then operates on a claim that never existed. Smallest fix: outermost-invocation marker (env var set on first claim; descendants skip claiming). A per-process source (herdr:norn:<pid>) also works if pane authority semantics allow it.

Minor

5. Any JSON without a top-level error is treated as acceptance. Response id is never matched to the request (the test server replies with id "ok" against requests named herdr:norn:<seq> — and the tests pass, which is itself the demonstration). A wrong-protocol or stale endpoint reads as success. Deserialize the documented envelope, require the matching id and a result.

6. The Coverage section reads as test coverage, but the paths are covered by live probe only. The three committed tests prove request serialization, in-process sequence ordering, and error surfacing — none construct a PaneClaim, exercise env parsing, drive any of the three execution paths, rotate a session, or test timeout/signal/overlap behavior (cargo test -p norn-cli herdr --lib: 3 tests, 523 filtered out). Reword, or better: fake-socket integration tests around each entry path, which would have caught findings 2–4.

Notes, non-blocking

  • The contract offers idle|working|blocked|unknown; the PR pins working for the entire session lifetime, so an idle TUI reads as working for hours. Fine as v1 scope — worth a follow-up.
  • Design question raised by Tom, under discussion with norn's regular reviewer (Sable Nightwick): whether the reporting seam should be general (env-configured presence/lifecycle reporting, HerdR as first consumer) rather than a HerdR-named module. The wire protocol is inherently HerdR's, but the seam — when to claim/release/update and what identity to report — could be neutral. Findings 2 and 3 land in that seam regardless of the answer, so the refactor question shouldn't block fixing them.

Provenance: first pass by an independent Norn session (GPT-5.6 Sol, session fa1da96a-3b09-4577-9631-7e305ec61eb5); every load-bearing claim re-verified by hand at the PR head before posting — rotation path, signal-handler absence, and claim-site census checked at the bytes; HerdR-side semantics checked against the 0.7.1 CLI and source.

@ijnebzor

Copy link
Copy Markdown
Collaborator Author

Lifecycle follow-up implemented

The pre-merge issues from the second review are addressed in 1f17c6e:

  • TUI claims start as idle, not permanently working.
  • Every centralized root turn emits TurnStartedworking and TurnFinishedidle; the Drop guard covers error/cancellation exits.
  • /new and /clear emit the new persisted session ID and JSONL path after rotation commits, and the active HerdR claim is refreshed.
  • Claim/update/release socket exchanges run via bounded spawn_blocking; synchronous Unix socket connect/read/write no longer run directly on executor workers.
  • Normal release is awaited; Drop uses a detached fallback only for exceptional cancellation/unwind.

Verification after these changes:

  • cargo fmt --all -- --check
  • cargo test -p norn-tui
  • cargo test -p norn-cli
  • cargo clippy -p norn-tui -p norn-cli --all-targets -- -D warnings
  • git diff --check

All pass.

@Deanofish

Copy link
Copy Markdown

Addendum: norn-side review from Sable Nightwick (norn's regular reviewer), folded in with permission — my posted review covered the HerdR side of the contract; this covers what only norn internals knowledge could see. Sable independently verified fmt, strict clippy, and the module tests green at head on norn's pinned toolchain.

Strengthens finding 2 (rotation staleness) — and it's worse than the TUI: print/driven /clear also rotates the live session in-process (apply_clear_requestClearOutcome::RotatedToNewSession, dispatched from the orchestrator), so long-running driven sessions advertise a retired id/path too, not just interactive TUIs. Sable independently ranked this the one blocking correctness item. Fix shape both reviews agree on: a PaneClaim update/re-report method invoked from the two rotation outcomes — and HerdR's separate pane.report_agent_session verb is purpose-built for exactly this (identity update without a lifecycle re-claim), so the revision should use it rather than re-sending report_agent.

Confirms finding 4 (nesting) is real, not hypothetical: norn's bash tool spawns sh -c inheriting the full parent env (it adds pairs, never clears), same for process-manager launches, hooks, and extensions — so the HERDR_* triple reaches every subprocess, and agents shelling out to norn --print is standing practice here. Per-process source (herdr:norn:<pid>) is endorsed from the norn side too, and gets nested semantics right for free: the child claims under its own source, and its release restores the parent as previous reporter.

Confirms finding 1 (connect timeout) at major severity with norn-side impact: both drivers run multi-thread tokio, so the blocking I/O doesn't starve the runtime — but the claim sits on the run's own critical path (TUI: before the alternate screen; print: before the first provider call), so a listening-but-not-accepting supervisor hangs norn at startup with a blank terminal, or at exit. Fix shapes: socket2 nonblocking connect + poll deadline, or move all pane I/O to a dedicated plain thread with bounded waits (which also fixes the teardown stall where a wedged HerdR delays the terminal error envelope, since the claim drops before emit_error_envelope).

New norn-side items:

  1. --no-session reports a phantom id. Ephemeral runs mint a fresh UUID that exists nowhere on disk and can't be resumed. agent_session_id is optional in the contract — omit the session fields for ephemeral runs so Agents-view consumers don't try to resume a phantom.
  2. The JSONL path derivation duplicates norn's canonical resolver. The rel_path/{id}.jsonl logic re-implements session_file_relative (session/persistence/io.rs), which is pub(crate) and validated. Faithful today, silent drift risk tomorrow — expose the resolver (or a resolved path on the session entry) and call it instead. (Identity itself checks out: info.session_id is authoritative on every persisted path including --fork and child-session resume, and the path is durably materialized by claim time.)
  3. Test helper violates house convention. must() calls std::process::abort() on failure — killing the whole test binary (523 sibling lib tests share the process) with no attribution. Convention here is #[allow(clippy::unwrap_used, ...)] on the #[cfg(test)] module with plain unwrap/expect.
  4. Cite the contract version for the 500 ms constant the way the field shapes do.

Design direction for the revision (answers the generalization question raised earlier): extract the seam, skip the trait. A norn-neutral presence/supervisor module owning the lifecycle facts — claim(identity) at driver start, update(identity) on rotation, optionally set_state at turn boundaries, release at teardown — with the HerdR transport as its single backend behind the HERDR_* env check, wire encoding staying honestly HerdR-named (it is HerdR's pane API; a generic protocol nobody speaks would be invented config). No trait while there's one implementor. The reason to cut the seam now: the rotation-update and state-fidelity work this review already requires are norn-lifecycle concerns, and they shouldn't be expressed in HerdR vocabulary at the call sites — the call sites are the expensive part to get right. A second supervisor, if ever, slots in at the seam without moving them.

Sable has offered a narrow re-read once the rotation-update and connect-timeout revisions land; I'll re-verify the HerdR side of the same revision.

@ijnebzor
ijnebzor marked this pull request as draft July 24, 2026 00:52
@ijnebzor

Copy link
Copy Markdown
Collaborator Author

Status correction after human review

Marking this draft again while the remaining Deanofish findings are addressed.

Current head 1f17c6e resolves:

  • sequence values returning the newly stored monotonic value
  • TUI idle/working lifecycle reporting
  • /new and /clear session identity/path refresh
  • synchronous socket work running off executor workers under a bounded await

Still unresolved and considered pre-merge work:

  1. Catchable signal cleanup for print/driven mode. Default SIGINT/SIGTERM termination does not run Rust destructors, so a claim may remain authoritative after an interrupted run.
  2. Nested/overlapping Norn authority. The fixed (source = herdr:norn, agent = norn) authority can allow one nested process to release another process's claim. We need either an outermost-invocation marker or validated per-process source semantics.
  3. Strict response-envelope validation. The client currently accepts any JSON response without a top-level error; it should require the matching request ID and a result, with a bounded response frame.
  4. Focused lifecycle tests. Full crate suites pass, but direct regression coverage should exercise claim/update/release, TUI lifecycle transitions, rotation, timeout, overlap, and signal behavior.

The PR should not merge until those are resolved or deliberately scoped out with documented HerdR behavior.

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