Skip to content

feat(agentbox): record who authorized a run, not just who it ran as - #1700

Merged
hsinatfootprintai merged 1 commit into
mainfrom
fix/1699-run-record-actor
Sep 3, 2026
Merged

feat(agentbox): record who authorized a run, not just who it ran as#1700
hsinatfootprintai merged 1 commit into
mainfrom
fix/1699-run-record-actor

Conversation

@hsinatfootprintai

@hsinatfootprintai hsinatfootprintai commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Closes #1699.

The gap

RunRecord shipped in #1672 with no actor. #1678 then landed Actor +
DelegationChain on audit rows — so the platform can attribute an API call
to a human, but a detached agent run holding a credential recorded nobody.

The system could answer "who called this API?" and not "who started the agent
that has been running on this box for six hours?"
— which is the asymmetry epic
#1680 exists to close, in the one place the work was already underway.

The correction this PR makes to its own issue

#1699's acceptance criteria said to populate the field "from the authenticated
context, never from caller-supplied input."
That is not implementable here.

agent-box has no authenticated context on either transport:

  • MCP — reached over SSH; the SSH session is the authentication
  • gRPC — a resident unix socket; access control is filesystem permissions,
    and SpawnServer.Spawn does not even take its context (_ context.Context)

So the honest contract is caller-asserted provenance, at the same trust level
as the command string beside it, and explicitly weaker than #1678's
server-resolved audit column. That is documented at every point it appears — the
struct field, the proto field, the MCP tool description, and process_list's
output, which renders Actor: alice@example.com (asserted).

Recording that distinction matters more than the field itself: two things both
called "actor", one cryptographically derived and one not, will otherwise be read
as equivalent evidence by whoever inherits this.

An empty actor stays empty rather than defaulting to the box user —
fabricating attribution nobody supplied is worse than recording none.

The upgrade hazard

RunRecordVersion 1 → 2. readRunRecord previously rejected any version !=
current:

if record.Version != RunRecordVersion { return …, fmt.Errorf("unsupported version") }

So the bump alone would have made every in-flight run unreadable at upgrade
time — including the unknown-outcome records that are the only evidence of runs
that died unresolved, which is exactly what #1672 exists to preserve.

It now accepts a range: v1 records load with empty actor fields and report as
unattributed; a newer-than-supported record is still rejected rather than guessed
at. Both directions are tested, and the range check was confirmed to fail the
test when neutered.

Tests

v1-reads-after-the-bump, future-version-rejected, actor round-trip, and
empty-actor-is-not-fabricated. Pre-existing on this branch and unrelated: two
TestStartSpawnListener_* failures on macOS only (socket path exceeds the
104-char sun_path limit; added by #1517, green on Linux CI).

Generated .pb.gw.go churn dropped per the repo convention; only .pb.go and
the .proto are committed.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Process launches can now include caller-provided actor and delegation-chain information.
    • Process listings display the asserted actor when available.
    • Attribution details are preserved in run records, including unattributed runs.
  • Compatibility

    • Existing run records remain readable while unsupported future versions are rejected.

Closes #1699.

RunRecord shipped in #1672 with no actor. #1678 then landed Actor +
DelegationChain on audit rows, so the platform can attribute an API call to
a human — but a detached agent run holding a credential recorded nobody.
The system could answer "who called this API?" and not "who started the
agent that has been running on this box for six hours?"

Adds Actor + DelegationChain to RunRecord, threaded through both transports
(process_start's MCP args and SpawnRequest's new fields), surfaced by
process_list, and carried in the run's on-disk record so a reconnecting
client sees it.

These fields are CALLER-ASSERTED, and are documented that way everywhere
they appear. The issue's own acceptance criteria said to populate them "from
the authenticated context, never from caller input" — that is not
implementable here: agent-box has no authenticated context on either
transport. It is reached over SSH (authenticated by the SSH session) or a
resident unix socket (by filesystem permissions); SpawnServer.Spawn does not
even take its context. So the honest contract is provenance at the same
trust level as the command string beside it, explicitly weaker than #1678's
server-resolved audit column. Recording that distinction matters more than
the field: two things both called "actor", one verified and one not, will
otherwise be read as equivalent evidence.

An empty actor stays empty rather than defaulting to the box user —
fabricating attribution nobody supplied is worse than recording none.

RunRecordVersion 1 -> 2. readRunRecord previously rejected any version !=
current, so the bump alone would have made every in-flight run unreadable at
upgrade time — including the "unknown"-outcome records that are the only
evidence of runs that died unresolved, which is precisely what #1672 exists
to preserve. It now accepts a RANGE: v1 records load with empty actor fields
and report as unattributed, while a newer-than-supported record is still
rejected rather than guessed at.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 82eeb5a2-29bb-49db-b9ad-cbc63a9e3a57

📥 Commits

Reviewing files that changed from the base of the PR and between 5b79d46 and 5f47ef1.

⛔ Files ignored due to path filters (1)
  • pkg/pb/containarium/v1/sandbox.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (5)
  • internal/agentbox/process.go
  • internal/agentbox/run_record.go
  • internal/agentbox/run_record_actor_test.go
  • internal/agentbox/spawn_server.go
  • proto/containarium/v1/sandbox.proto

Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The change adds caller-asserted actor and delegation-chain fields to process spawning, persists them in version 2 run records, preserves version 1 readability, and exposes asserted actors in process listings.

Changes

Run provenance attribution

Layer / File(s) Summary
Run record contract and compatibility
proto/containarium/v1/sandbox.proto, internal/agentbox/run_record.go
SpawnRequest and RunRecord now support actor and delegation-chain fields. Run record readers accept versions 1 through 2 and reject unsupported versions.
Provenance propagation and persistence
internal/agentbox/process.go, internal/agentbox/spawn_server.go
Process start forwards provenance through background process creation, persists it in RunRecord, and shows asserted actors in process_list.
Compatibility and attribution validation
internal/agentbox/run_record_actor_test.go
Tests cover legacy records, future-version rejection, attribution round trips, and empty attribution preservation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 5f47e

This change adds caller-asserted run attribution across spawn paths, persistence, and process listings while retaining v1 record compatibility and rejecting unsupported future versions. No merge-blocking risk remains.

Sequence Diagram(s)

sequenceDiagram
  participant SpawnRequest
  participant process_start
  participant spawnBackgroundProcess
  participant RunRecord
  participant process_list
  SpawnRequest->>process_start: actor and delegation_chain
  process_start->>spawnBackgroundProcess: provenance values
  spawnBackgroundProcess->>RunRecord: persist provenance
  process_list->>RunRecord: read run record
  RunRecord-->>process_list: asserted actor
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements actor persistence, transport propagation, process listing, and v1 compatibility. However, it uses caller-asserted provenance instead of authenticated context, and the reviewed change… Populate actor and delegation data from authenticated context rather than caller-supplied fields. Add actor data to run start and exit audit events. Confirm that the stored actor representation matches the shape required by issue #1699.
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 4 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: recording the authorizing actor for agent runs.
Out of Scope Changes check ✅ Passed The reviewed changes are related to actor attribution, provenance propagation, run-record versioning, compatibility, and process listing. No unrelated code changes are evident.
Full details: Linked Issues check

Explanation

The PR implements actor persistence, transport propagation, process listing, and v1 compatibility. However, it uses caller-asserted provenance instead of authenticated context, and the reviewed changes do not add run start and exit audit events carrying actor data. These requirements are explicit in issue #1699.

Full details: Docstring Coverage

Explanation

Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 4 files. (1 skipped: 1 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1699-run-record-actor

Comment @coderabbitai help to get the list of available commands.

@hsinatfootprintai
hsinatfootprintai merged commit 41560ff into main Sep 3, 2026
9 checks passed
@hsinatfootprintai
hsinatfootprintai deleted the fix/1699-run-record-actor branch September 3, 2026 09:40
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.

RunRecord has no actor — a detached agent run records who it ran as, but not who authorized it

2 participants