Skip to content

feat(aid): name the agent so a session manager can see it - #550

Merged
blooop merged 5 commits into
mainfrom
feat/herdr-agent
Sep 1, 2026
Merged

blooop merged 5 commits into
mainfrom
feat/herdr-agent

Conversation

@blooop

@blooop blooop commented Sep 1, 2026 •

Copy link
Copy Markdown
Owner

Every aid launch now exports HERDR_AGENT=<agent> for the session it opens, so
herdr reports a workspace agent as idle, working or blocked
instead of showing nothing at all.

Closes #548, which #549 measured out first.

Why it is this small

The map proposed a generic forwarding facility: DEVLAUNCH_FORWARD_ENV,
DEVLAUNCH_FORWARD_SOCKET, and the lend path reused for a manager's own CLI.
The measurement
says none of it is needed, because the gap was narrower than the map thought.

A manager learns what an agent is doing in two steps. It decides which agent a
pane holds, usually from the foreground process, and only then matches that
agent's rules against the pane's screen. Under aid the foreground processes are
aid, ssh and two devpods with no claude anywhere, so the first step failed
and took the second with it.

The second step was never broken. dl <ws> -- <agent> pipes the agent's own TUI
through the pane, so the pane holds the real screen. Only the name was missing.

Measured, not reasoned about

Both before the change and after, on hardware, because the plausible-looking
version of this is a silent no-op.

It reaches a manager through the ssh child, not through aid. A setenv after
start does not rewrite /proc/<pid>/environ, which the kernel fixes at exec, so:

  aid   (pid 37015): (absent)
  ssh   (pid 37102): HERDR_AGENT=claude
  devpod (pid 37104): HERDR_AGENT=claude
  devpod (pid 37116): HERDR_AGENT=claude

herdr walks the pane's foreground processes, which is what makes the child enough.
With the patched aid and nothing in the launching environment:

t=05s agent=None    status=unknown | ['aid', 'devpod']
t=10s agent=claude  status=working | ['aid', 'ssh', 'devpod', 'devpod']
t=15s agent=claude  status=idle    | ['aid', 'ssh', 'devpod', 'devpod']

and into a permission form, tracking Claude's own screen through the container:

t=04s claude blocked | rule: live_blocked_form (region=after_last_horizontal_rule)

The scratch workspace was deleted afterwards and devpod list is back to what it
was.

The three choices worth arguing about

Written on every launch, over whatever was there. aid is what decides
which agent starts, so it is the only thing that can be right about it. A
HERDR_AGENT=codex left in a profile is wrong the moment aid --claude runs.

No detection first. Gating on HERDR_ENV=1 was the alternative. It buys
nothing and adds a second thing to be wrong about, which fails silently, which is
the failure this whole issue is about. A machine with no herdr pays one setenv.

aid only, never plain dl. dl <ws> -- claude typed by hand is your
command, and reading an agent back out of a command tail would be a guess. Same
reasoning that puts IS_SANDBOX in aid's table rather than in dl.

What this does not do

HERDR_AGENT is herdr's name, and it is the one manager-specific word in either
binary, which #548 explicitly did not want. That constraint was an argument for
the generic facility; with the facility unnecessary, one name is what is left.
A manager with a different override still needs it set some other way.

Nothing is forwarded into the container. The variable is set on the host, for the
host process a manager inspects.

Tests

Three in rust/aid/src/main.rs, serialised on a mutex and restoring the variable:
every name in aid's table is written verbatim (herdr matches manifest ids, so
claude classifies and Claude does not), a stale value is replaced, and a line
that starts no agent leaves the environment untouched.

🤖 Generated with Claude Code

Summary by Sourcery

Have aid name the agent it launches so session managers can identify and monitor the workspace correctly.

New Features:

  • Make aid identify the selected agent to session managers through HERDR_AGENT, enabling workspace status reporting such as idle, working, and blocked.

Documentation:

  • Document the new HERDR_AGENT behavior, its scope, and how it interacts with aid and dl.

Tests:

  • Add coverage for agent naming, replacement of stale values, no-agent launches, and environment restoration during test failures.

Chores:

  • Record the session-manager agent identification behavior in the changelog.

Every aid launch exports HERDR_AGENT=<agent> for the session it opens, so
herdr reports a workspace agent as idle, working or blocked instead of
nothing at all.

The gap was narrower than it looked. A manager first decides which agent a
pane holds, usually from the foreground process, and only then matches that
agent's rules against the pane's screen. Under aid the foreground processes
are aid, ssh and two devpods with no claude anywhere, so the first step
failed and took the second with it. The second step was never broken:
`dl <ws> -- <agent>` pipes the agent's own TUI through the pane. Only the
name was missing.

Measured on hardware before and after (#549). It reaches a manager through
the ssh child, not through aid: a setenv after start does not rewrite
/proc/<pid>/environ, so aid's own row carries nothing and the ssh it spawns
carries HERDR_AGENT=claude. herdr walks the pane's foreground processes,
which is what makes the child enough.

Closes #548.

@sourcery-ai sourcery-ai 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.

Sorry @blooop, you've used your own review budget of 250,000 diff characters for the last 7 days.

You can request another review in 3 days by commenting @sourcery-ai review. Upgrade to get a review now.

@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

This PR makes aid export the selected agent as host-side HERDR_AGENT immediately before invoking dl, allowing session managers such as herdr to identify the agent through inherited SSH child processes without adding generic environment forwarding; it also documents the behavior and tests replacement, no-op, and per-agent naming semantics.

Sequence diagram for aid naming an agent for session managers

sequenceDiagram
    participant User
    participant Aid as aid
    participant DL as dl
    participant SSH as ssh child
    participant Manager as Session manager

    User->>Aid: launch with selected agent
    Aid->>Aid: parsed.agent()
    alt agent selected
        Aid->>Aid: set_var HERDR_AGENT=<agent>
        Aid->>DL: run(&dl_args)
        DL->>SSH: exec with inherited HERDR_AGENT
        Manager->>SSH: inspect foreground processes
        Manager->>Manager: identify agent and classify pane screen
    else no agent selected
        Aid->>DL: run(&dl_args)
        Note over Aid,DL: HERDR_AGENT is unchanged
    end
Loading

Flow diagram for aid agent environment naming

flowchart TD
    A[aid parses launch] --> B{Agent selected?}
    B -->|Yes| C[Overwrite HERDR_AGENT with selected agent]
    C --> D[Invoke dl]
    D --> E[ssh child inherits HERDR_AGENT]
    E --> F[Session manager identifies agent]
    F --> G[Apply agent rules to pane screen]
    B -->|No| H[Leave HERDR_AGENT unchanged]
Loading

File-Level Changes

Change Details Files
Propagate the selected agent name from aid to the host session environment.
  • Add the HERDR_AGENT constant and set it after argument parsing, before dl launches its child processes.
  • Only write the variable when aid selected an agent; leave it unchanged for agentless or retired invocations.
  • Document that the value is host-side, overwritten on each agent launch, and not set by plain dl.
  • Expose the new environment variable in aid help output and project documentation.
rust/aid/src/main.rs
README.md
docs/workspace-tools.md
Add coverage for environment propagation and preservation semantics.
  • Serialize environment-mutating tests and restore the prior value after each test.
  • Verify every configured agent name is written verbatim.
  • Verify stale values are replaced and agentless launches leave the environment untouched.
rust/aid/src/main.rs
Document the session-manager integration and its rationale in release notes.
  • Explain why identifying the agent through the inherited SSH child fixes manager status detection.
  • Record the unconditional write, aid-only scope, and lack of container forwarding.
CHANGELOG.md

Assessment against linked issues

Issue Objective Addressed Explanation
#548 Allow session managers to identify and monitor agents started through devlaunch, including launches made with plain dl, without requiring manager-specific changes. ❌ The PR only sets the manager-specific HERDR_AGENT variable from aid. It does not help managers using other environment variables or hook protocols, and plain dl <workspace> -- <agent> launches remain unsupported.
#548 Provide the proposed generic transport facilities for forwarding manager coordinates and Unix sockets, and make the manager CLI available inside the workspace when required. ❌ The PR deliberately does not implement DEVLAUNCH_FORWARD_ENV, DEVLAUNCH_FORWARD_SOCKET, SSH remote forwarding, ControlMaster keying changes, or the lending path for manager binaries. Those facilities may be unnecessary for herdr based on the reported measurement, but they remain part of the issue's general solution.
#548 Document and test the chosen session-manager integration behavior, including its scope and launch conditions. ✅

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

aid_names_the_agent_it_starts said it held 'every name in aid's own
table is one of herdr's ids ... true as agents are added'. The body
round-trips set_var over the table's names, which is true of every
string and asserts nothing about herdr's ids. No such list exists in
this tree to diff against, so the sentence was a second copy of a fact
with no guard beside it, in the shape CLAUDE.md's standing rule names.

The doc now claims only what the body proves: the name reaches the
environment verbatim, which is worth pinning because a transformation
fails silently at herdr's end. The convention the sentence was reaching
for moves to the AGENTS table in rewrite.rs, where a row is added and
where somebody adding one will read it.
name_agent_for_session_manager's doc explained that the variable reaches
herdr through the ssh child rather than through aid, and closed the
paragraph with (aid_names_the_agent_it_starts). By this repo's
convention a cited test name is the guard pinning the behaviour just
described, and that test pins nothing of the kind -- it round-trips
set_var in one process. The citation resolves, so the guard that checks
citations is satisfied while the sentence is misleading, which is the
worse of the two failures.

Nothing in this tree can hold that fact: it is about a running herdr, a
pty and a container. The comment now says so and points at the /proc
reading posted on #548, which is where the evidence actually is.
'nothing you set there survives an aid launch' is false on every line
aid does not start an agent on: HERDR_AGENT=mine aid <ws> --autorm
parses to Task::Retired, agent() is None, and mine survives -- which is
what a_line_that_starts_no_agent_leaves_the_environment_alone already
asserts, so the README contradicted a test on the same branch. Same for
--help, --version and every parse refusal.

It also said dl writes the variable, where docs/workspace-tools.md on
the same branch says plain dl sets nothing. aid alone writes it, and the
sentence now says which lines leave a value of yours standing. The docs
page's own 'every aid launch' gets the same qualifier it was relying on
a later paragraph to supply.
with_the_environment promised the variable was 'saved and restored,
whatever it does', and restored it on the line after body(). An
assertion that fails unwinds past a line, so the promise held for
exactly the case nobody needs it and broke for the one they do: the
failing test leaked its value, and the next failure would be a test that
never touched the variable.

The restore is a Drop now, so it runs on the unwind, and the guard is
held across it. a_body_that_panics_still_restores_the_environment is red
without it -- 'a failed test leaked its value into the tests after it'
-- and green with it.

No victim today, since nothing else in this binary reads the variable.
Filed because the comment claimed the guarantee, and the next test to
read it would have inherited the bug rather than found it.
@codecov

codecov Bot commented Sep 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.41270% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.92%. Comparing base (95a594d) to head (f43479e).

Files with missing lines Patch % Lines
rust/aid/src/main.rs 98.41% 1 Missing ⚠️
Additional details and impacted files
Flag Coverage Δ
python 42.98% <ø> (ø)
rust 95.21% <98.41%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
shipped code (rust) 95.21% <98.41%> (+0.01%) ⬆️
harness and tooling (python) 42.98% <ø> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@blooop
blooop merged commit ffaadb5 into main Sep 1, 2026
15 checks passed
@blooop
blooop deleted the feat/herdr-agent branch September 1, 2026 21:26
@blooop blooop mentioned this pull request Sep 1, 2026
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.

dl carries two tokens into a workspace and nothing else, so no session manager can see the agent it started

1 participant