feat(agents): surface managed Codex agent runs - #6416
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
| @@ -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); | |||
There was a problem hiding this comment.
🟡 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 = |
There was a problem hiding this comment.
🟡 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` }}> |
There was a problem hiding this comment.
🟡 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.
| <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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ 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.
ApprovabilityVerdict: 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. |
There was a problem hiding this comment.
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
| reason: Schema.Literals(["thread-not-found", "spawn-failed", "run-not-found", "not-owned"]), | ||
| threadId: Schema.optional(ThreadId), | ||
| agentId: Schema.optional(TrimmedNonEmptyString), |
There was a problem hiding this comment.
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:
| 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", |
There was a problem hiding this comment.
🟡 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.
There was a problem hiding this comment.
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"]), |
There was a problem hiding this comment.
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.randomUUIDv4failure (line 162) ->spawn-failedtask.startedingest failure (line 238) ->spawn-failedchild.kill()failure (line 330) ->not-owned, so a run that is owned but could not be killed surfaces asManaged 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

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 execruns 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
git diff --checkBuilt 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 execruns with WebSocket RPC (launchManagedCodexExec,cancelManagedAgent), aManagedCodexExecservice that spawns processes and pushes lifecycle events throughingestRuntimeEvent, and operate-scope auth. Launch errors redact prompts from wire payloads.Improves native Codex collab agents by correlating
spawnAgenttool calls to model/effort/parent, emittingcollabAgent/metadatafor late identity patches, and upsertingtask-identity:*activities so terminal rows keep hierarchy without reopening. Payloads gainagentSourceandcancellationOwner.Updates the Agents UI and client model with
childrenByParentId, nested tree rendering, and a stop control whencancellationOwner === '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
ManagedCodexExecservice that spawnscodex execas a child process, streams stdout/stderr astask.progressevents, and emitstask.started/task.completedwith rich status and summaries.launchManagedCodexExecandcancelManagedAgentWebSocket RPC endpoints (with orchestration scope authorization) and wires client-side command atoms increateOrchestrationEnvironmentAtoms.collabAgent/metadataevents carrying enriched child agent identity (model, effort, parentAgentId), correlated to the originating turn/item.agentSourceandcancellationOwnerfields toRuntimeSubagentandTaskAgentLinkage, and buildschildrenByParentIdinAgentPanelModelfor non-workflow parent-child relationships.AgentsPaneland shows a stop button for managed agents in active states, dispatchingcancelManagedAgenton click.Macroscope summarized e1f841c.