Skip to content

feat(agents): surface managed Codex agent runs - #6416

Open
Juliusicon wants to merge 7 commits into
pingdotgg:mainfrom
Juliusicon:feature/codex-agent-sidebar
Open

feat(agents): surface managed Codex agent runs#6416
Juliusicon wants to merge 7 commits into
pingdotgg:mainfrom
Juliusicon:feature/codex-agent-sidebar

Conversation

@Juliusicon

@Juliusicon Juliusicon commented Aug 13, 2026

Copy link
Copy Markdown

What changed

Adds first-class visibility and lifecycle ownership for Codex agents in T3 Code. Native Codex subagents retain parent/child hierarchy, model, effort, and late-arriving metadata. T3-managed codex exec runs use typed RPC contracts, stable IDs, lifecycle events, exact process-tree cancellation, and sidebar controls.

Why

Raw nested CLI agents were invisible to T3 and could survive supervisor cancellation. Native lifecycle rows also lost hierarchy or metadata under retention and out-of-order event delivery.

Impact

Users can see native and T3-managed Codex agents in the Agents panel, including nested ownership and terminal state. Only T3-managed CLI runs are surfaced; arbitrary external processes are intentionally not discovered by process scanning.

Checks

  • Focused unit/replay tests: 80/80
  • Runtime integration tests: 3/3
  • Server and client-runtime typechecks
  • Targeted lint and formatting
  • git diff --check
  • Final adversarial GPT-5.6 Terra review: PASS

Built with GPT-5.6 Sol through the Codex harness; reviewed with GPT-5.6 Terra.


Note

Medium Risk
Spawns child processes with user prompts and operate-scoped RPC; changes are well-tested but touch orchestration ingestion, Codex event mapping, and cancellation ownership.

Overview
Adds T3-owned codex exec runs with WebSocket RPC (launchManagedCodexExec, cancelManagedAgent), a ManagedCodexExec service that spawns processes and pushes lifecycle events through ingestRuntimeEvent, and operate-scope auth. Launch errors redact prompts from wire payloads.

Improves native Codex collab agents by correlating spawnAgent tool calls to model/effort/parent, emitting collabAgent/metadata for late identity patches, and upserting task-identity:* activities so terminal rows keep hierarchy without reopening. Payloads gain agentSource and cancellationOwner.

Updates the Agents UI and client model with childrenByParentId, nested tree rendering, and a stop control when cancellationOwner === 't3'.

Reviewed by Cursor Bugbot for commit e1f841c. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Surface managed Codex agent runs with launch, cancel, and hierarchical UI

  • Adds ManagedCodexExec service that spawns codex exec as a child process, streams stdout/stderr as task.progress events, and emits task.started/task.completed with rich status and summaries.
  • Exposes launchManagedCodexExec and cancelManagedAgent WebSocket RPC endpoints (with orchestration scope authorization) and wires client-side command atoms in createOrchestrationEnvironmentAtoms.
  • Extends the Codex provider adapter to emit collabAgent/metadata events carrying enriched child agent identity (model, effort, parentAgentId), correlated to the originating turn/item.
  • Adds agentSource and cancellationOwner fields to RuntimeSubagent and TaskAgentLinkage, and builds childrenByParentId in AgentPanelModel for non-workflow parent-child relationships.
  • Renders agents as an indented hierarchy in AgentsPanel and shows a stop button for managed agents in active states, dispatching cancelManagedAgent on click.
  • Risk: cancellation relies on SIGTERM with a 3-second force-kill; processes that ignore SIGTERM will be hard-killed after the timeout with no further retry.

Macroscope summarized e1f841c.

@coderabbitai

coderabbitai Bot commented Aug 13, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c22faf77-495f-493a-b8a5-de4f794f7636

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XL 500-999 changed lines (additions + deletions). labels Aug 13, 2026
Comment on lines 772 to +785
@@ -751,6 +779,10 @@ export function deriveAgentPanelModel({
const list = members.get(agent.parentAgentId) ?? [];
list.push(agent);
members.set(agent.parentAgentId, list);
} else if (agent.parentAgentId !== null && agentIds.has(agent.parentAgentId)) {
const list = childrenByParentId.get(agent.parentAgentId) ?? [];
list.push(agent);
childrenByParentId.set(agent.parentAgentId, list);

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.

🟡 Medium state/subagentRuntime.ts:772

Cyclic parent relationships are omitted from directAgents without a render root, so self-parented or mutually parented agents disappear from the panel entirely. The childrenByParentId branch only checks whether the parent ID exists; detect cycles while building it and treat cyclic nodes as orphaned roots (or otherwise break the cycle).

   const agentIds = new Set(source.map((agent) => agent.id));
+  const parentById = new Map(source.map((agent) => [agent.id, agent.parentAgentId] as const));
 
   for (const agent of source) {
@@
-    } else if (agent.parentAgentId !== null && agentIds.has(agent.parentAgentId)) {
+    } else if (
+      agent.parentAgentId !== null &&
+      agentIds.has(agent.parentAgentId) &&
+      !(() => {
+        const seen = new Set<string>();
+        let current: string | null = agent.id;
+        while (current !== null && agentIds.has(current)) {
+          if (seen.has(current)) return true;
+          seen.add(current);
+          current = parentById.get(current) ?? null;
+        }
+        return false;
+      })()
+    ) {
🤖 Copy this AI Prompt to have your agent fix this:
In file @packages/client-runtime/src/state/subagentRuntime.ts around lines 772-785:

Cyclic parent relationships are omitted from `directAgents` without a render root, so self-parented or mutually parented agents disappear from the panel entirely. The `childrenByParentId` branch only checks whether the parent ID exists; detect cycles while building it and treat cyclic nodes as orphaned roots (or otherwise break the cycle).

collabSpawnMetadata.set(childThreadId, metadata);
}
yield* Ref.set(collabSpawnMetadataRef, collabSpawnMetadata);
const enrichedChildren =

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.

🟡 Medium Layers/CodexSessionRuntime.ts:1314

Late spawnAgent metadata for a child-scoped item/started or item/completed notification never reaches ingestion or replay. interceptCollabChildNotification returns at line 1343 before the collabAgent/metadata loop, so nested-agent rows retain missing hierarchy, model, and effort fields even though the in-memory state is updated. Emit the metadata patch before this early return.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/CodexSessionRuntime.ts around line 1314:

Late `spawnAgent` metadata for a child-scoped `item/started` or `item/completed` notification never reaches ingestion or replay. `interceptCollabChildNotification` returns at line 1343 before the `collabAgent/metadata` loop, so nested-agent rows retain missing hierarchy, model, and effort fields even though the in-memory state is updated. Emit the metadata patch before this early return.

const cyclic = ancestors.has(agent.id);
const nextAncestors = new Set(ancestors).add(agent.id);
return (
<div style={{ paddingLeft: `${Math.min(depth, 6) * 12}px` }}>

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.

🟡 Medium components/AgentsPanel.tsx:230

Deep agent trees are indented quadratically, so a depth-6 row receives 252px of total padding instead of the intended capped indentation and its content is unnecessarily squeezed or hidden. Because each child is nested inside its parent’s padded <div> while this line applies 12px * depth again, use a constant per-level padding for nested wrappers.

Suggested change
<div style={{ paddingLeft: `${Math.min(depth, 6) * 12}px` }}>
<div style={{ paddingLeft: depth > 0 ? "12px" : "0px" }}>
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/AgentsPanel.tsx around line 230:

Deep agent trees are indented quadratically, so a depth-6 row receives 252px of total padding instead of the intended capped indentation and its content is unnecessarily squeezed or hidden. Because each child is nested inside its parent’s padded `<div>` while this line applies `12px * depth` again, use a constant per-level padding for nested wrappers.

@macroscopeapp macroscopeapp Bot left a comment

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.

Effect service conventions review of the new ManagedCodexExec service and its contract error. Findings are limited to the service definition shape, construction/layer exports, dependency acquisition, and error modeling.

Posted via Macroscope — Effect Service Conventions

Comment thread apps/server/src/orchestration/ManagedCodexExec.ts Outdated
Comment thread apps/server/src/orchestration/ManagedCodexExec.ts Outdated
Comment thread packages/contracts/src/orchestration.ts Outdated
Comment thread apps/server/src/orchestration/ManagedCodexExec.ts Outdated
Comment thread apps/server/src/orchestration/ManagedCodexExec.ts Outdated

@cursor cursor Bot left a comment

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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want fixes drafted automatically? Bugbot Autofix can create code changes for findings. A team admin can enable Autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 865817f. Configure here.

Comment thread apps/server/src/provider/Layers/CodexSessionRuntime.ts
@macroscopeapp

macroscopeapp Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Needs human review

4 blocking correctness issues found. This PR introduces a substantial new feature for managed Codex agent runs, including process spawning/cancellation, new RPC endpoints, and UI agent tree management with cancel actions. New capability of this scope warrants human review to validate the feature design and cross-system integration.

You can customize Macroscope's approvability policy. Learn more.

@macroscopeapp macroscopeapp Bot left a comment

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.

One convention finding: ManagedAgentRunError now carries good structural attributes, but every wrapping site discards the underlying failure entirely, so no cause survives. Details inline.

Posted via Macroscope — Effect Service Conventions

Comment on lines +67 to +69
reason: Schema.Literals(["thread-not-found", "spawn-failed", "run-not-found", "not-owned"]),
threadId: Schema.optional(ThreadId),
agentId: Schema.optional(TrimmedNonEmptyString),

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.

ManagedAgentRunError has no cause, and every wrapping site in ManagedCodexExec.ts (getThreadDetailById, getProjectShellById, randomUUIDv4, spawner.spawn, child.kill) maps with () => new ManagedAgentRunError({...}), so the immediate platform/spawn failure and its stack are dropped with no diagnostic left anywhere. Convention here is to keep the structural fields and preserve the real failure as cause (as sibling contract errors such as VcsProcessSpawnError and GitCommandError do).

Suggest adding an optional cause and passing the underlying error at each mapError:

Suggested change
reason: Schema.Literals(["thread-not-found", "spawn-failed", "run-not-found", "not-owned"]),
threadId: Schema.optional(ThreadId),
agentId: Schema.optional(TrimmedNonEmptyString),
reason: Schema.Literals(["thread-not-found", "spawn-failed", "run-not-found", "not-owned"]),
threadId: Schema.optional(ThreadId),
agentId: Schema.optional(TrimmedNonEmptyString),
cause: Schema.optional(Schema.Defect()),

If the redaction guarantee asserted in ManagedCodexExec.test.ts means the encoded wire payload must stay free of the spawn command, keep the cause server-side instead (e.g. Effect.tapCause(Effect.logError) before the mapError) rather than discarding it.

Posted via Macroscope — Effect Service Conventions

Effect.mapError(
(cause) =>
new ManagedCodexExecInternalError({
reason: "not-owned",

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.

🟡 Medium orchestration/ManagedCodexExec.ts:330

A failure from run.child.kill(...) is reported as not-owned even though ownership was verified immediately above, so process-control failures such as an inability to signal the child are exposed through toManagedAgentRunError as ownership denials. Wrap this failure with the cancellation/process-control error reason instead.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/orchestration/ManagedCodexExec.ts around line 330:

A failure from `run.child.kill(...)` is reported as `not-owned` even though ownership was verified immediately above, so process-control failures such as an inability to signal the child are exposed through `toManagedAgentRunError` as ownership denials. Wrap this failure with the cancellation/process-control error reason instead.

@macroscopeapp macroscopeapp Bot left a comment

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.

Reviewed the new/changed Effect service code (ManagedCodexExec.ts, Services/ProviderRuntimeIngestion.ts, ws.ts wiring, contracts). Service definition (inline Context.Service interface, make then layer), environment-based dependency acquisition (ProviderRuntimeIngestionService, ProjectionSnapshotQuery, ChildProcessSpawner), namespace imports, and cause preservation all match the conventions now. One error-modeling issue remains: the reason discriminator does not identify the failing stage.

Posted via Macroscope — Effect Service Conventions

export class ManagedCodexExecInternalError extends Schema.TaggedErrorClass<ManagedCodexExecInternalError>()(
"ManagedCodexExecInternalError",
{
reason: Schema.Literals(["thread-not-found", "spawn-failed", "not-owned"]),

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.

reason is this error's stage/operation discriminator and it also becomes the caller-visible ManagedAgentRunError.reason (via toManagedAgentRunError), but four structurally different failures are folded into reasons that describe something else:

  • projection query failure (lines 127, 144) -> thread-not-found, indistinguishable from the genuine missing-thread branches (lines 135, 152)
  • crypto.randomUUIDv4 failure (line 162) -> spawn-failed
  • task.started ingest failure (line 238) -> spawn-failed
  • child.kill() failure (line 330) -> not-owned, so a run that is owned but could not be killed surfaces as Managed agent run failed (not-owned)

Since the message and client behavior are derived from reason, suggest giving each wrapping site a stage-accurate literal (e.g. thread-lookup-failed, agent-id-failed, ingest-failed, cancel-failed) and extending/mapping ManagedAgentRunError.reason in packages/contracts/src/orchestration.ts accordingly, so the caller-visible reason matches the failure that actually occurred.

Posted via Macroscope — Effect Service Conventions

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

Labels

size:XL 500-999 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant