feat(launch): export the composed role for commit attribution #287

Merged
coilysiren merged 1 commit from aos/claude/xu59-role-attribution into main 2026-08-16 04:02:25 +00:00
Member

Half two of two. Pairs with coilyco-flight-deck/infrastructure #851, which adds the Agent-Role trailer this supplies the value for.

Merge #851 first. It is a no-op without this, and this is inert without it, so either order is safe. That one first keeps the trailer machinery ready before any value starts arriving.

Why this binary

Agent-Role has no other source. The estate records which model wrote a commit about 1,617 times and which role about 96, so #286 (Director defers no execution boundary) cannot be checked against the tree. Only the process that resolved the bundle knows which role a session is running.

Both exec paths carry it

  • native launch already had role in scope, since it is caller-assigned.
  • refresh-then-exec reads it from the manifest of the bundle it just composed, via bundle.ReadManifest.

Three cases that deliberately attribute nothing

Stamping a wrong role is worse than stamping none, because a reader cannot distinguish an absent trailer from an incorrect one. So:

  • A fallback launch. It reuses a last-known-good projection this run did not compose, so the role in it is not known to be this session's.
  • A nested launch. It skips refresh and inherits whatever the outer launch exported.
  • An empty or whitespace role. Returns no environment at all rather than an empty assignment.

The lookup I did not use

~/.agent-compose/projection.json names an active bundle whose manifest carries role, the seat name, and pronouns. It looks like this could be a pure infrastructure change with no work here.

It cannot. That file is host-global. While writing this it named engineer in a session running ai, because it reflects the last thing projected rather than any one session. A shim reading it would mis-attribute silently. That is the whole reason the value has to leave from this process, and it is why the existing coauthor travels the same way.

Verified

go build, go vet, and the full go test ./... pass. Three tests added covering the round trip and the attributes-nothing cases.

Cross-repository check, run against both working trees: the constant this exports and the variable the shim reads are the same string, and feeding it through the shim's injector produces Agent-Role: director.

Tracked at coilysiren/inbox#362, consumer #286.

🤖 Generated with Claude Code

Half two of two. Pairs with `coilyco-flight-deck/infrastructure` #851, which adds the `Agent-Role` trailer this supplies the value for. **Merge #851 first.** It is a no-op without this, and this is inert without it, so either order is safe. That one first keeps the trailer machinery ready before any value starts arriving. ## Why this binary `Agent-Role` has no other source. The estate records which model wrote a commit about 1,617 times and which role about 96, so `#286` (Director defers no execution boundary) cannot be checked against the tree. **Only the process that resolved the bundle knows which role a session is running.** ## Both exec paths carry it * **native launch** already had `role` in scope, since it is caller-assigned. * **refresh-then-exec** reads it from the manifest of the bundle it just composed, via `bundle.ReadManifest`. ## Three cases that deliberately attribute nothing **Stamping a wrong role is worse than stamping none**, because a reader cannot distinguish an absent trailer from an incorrect one. So: * **A fallback launch.** It reuses a last-known-good projection this run did not compose, so the role in it is not known to be this session's. * **A nested launch.** It skips refresh and inherits whatever the outer launch exported. * **An empty or whitespace role.** Returns no environment at all rather than an empty assignment. ## The lookup I did not use `~/.agent-compose/projection.json` names an active bundle whose manifest carries `role`, the seat name, and pronouns. It looks like this could be a pure infrastructure change with no work here. It cannot. That file is **host-global**. While writing this it named `engineer` in a session running `ai`, because it reflects the last thing projected rather than any one session. A shim reading it would mis-attribute silently. That is the whole reason the value has to leave from this process, and it is why the existing coauthor travels the same way. ## Verified `go build`, `go vet`, and the full `go test ./...` pass. Three tests added covering the round trip and the attributes-nothing cases. Cross-repository check, run against both working trees: the constant this exports and the variable the shim reads are the same string, and feeding it through the shim's injector produces `Agent-Role: director`. Tracked at `coilysiren/inbox#362`, consumer `#286`. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Agent-Role has no source but this binary. A commit knows which model
wrote it about 1,617 times across the estate and which role about 96, so
a charter that draws a line at execution cannot be checked against the
tree. infrastructure's git attribution shim gained an Agent-Role trailer;
this supplies the value it reads.

Both exec paths carry it. The native launch already had the role in
scope, since it is caller-assigned. The refresh-then-exec path reads it
from the manifest of the bundle it just composed.

A fallback launch does not. It reuses a last-known-good projection this
run did not compose, so the role in it is not known to be this session's,
and an empty role attributes nothing. Stamping a wrong role is worse than
stamping none, because a reader cannot tell an absent trailer from an
incorrect one. A nested launch skips refresh and likewise adds nothing,
inheriting whatever the outer launch already exported.

Why this binary rather than a lookup: the obvious alternative is reading
~/.agent-compose/projection.json, whose bundle manifest carries the role.
That file is host-global. While writing this it named `engineer` in a
session running `ai`, because it reflects the last projection rather than
any one session. Only the process that resolved the bundle knows which
role a session is running, so the value has to leave from here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Author
Member

The missing checks are a repository gap, not this pull request. Lucia (AI Engineer seat), 2026-08-15.

Kai asked why no CI appeared here. release.yml is the only workflow in this repository and triggers on push: branches: [main] plus workflow_dispatch, so grep -rn pull_request .forgejo/ returns nothing and no pull request here has ever shown a check.

Fixed in #288, which runs the same ward exec test the release job runs.

Merge #288 first, then rebase this, and it becomes the first pull request in this repository to actually get validated. Until then the only evidence for this change is my local run: go build, go vet, and the full go test ./..., plus a cross-repository check that the constant exported here and the variable read by coilyco-flight-deck/infrastructure #851 are the same string.

**The missing checks are a repository gap, not this pull request.** Lucia (AI Engineer seat), 2026-08-15. Kai asked why no CI appeared here. `release.yml` is the only workflow in this repository and triggers on `push: branches: [main]` plus `workflow_dispatch`, so `grep -rn pull_request .forgejo/` returns nothing and **no pull request here has ever shown a check.** Fixed in #288, which runs the same `ward exec test` the release job runs. **Merge #288 first, then rebase this**, and it becomes the first pull request in this repository to actually get validated. Until then the only evidence for this change is my local run: `go build`, `go vet`, and the full `go test ./...`, plus a cross-repository check that the constant exported here and the variable read by `coilyco-flight-deck/infrastructure` #851 are the same string.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-flight-deck/agent-compose!287
No description provided.