Skip to content

Proposal: add task and plan lifecycle hooks with external harness-driven plan updates #24547

@ashione

Description

@ashione

Summary

I would like to propose adding task and plan lifecycle hooks to Codex, together with a small external plan update API for harness-driven workflows.

I already have a working prototype in my fork, but I read docs/contributing.md and saw that external code contributions are by invitation only. So I am opening this issue first to check whether this direction fits the project before asking for PR review.

Fork PR:

ashione#1

Upstream compare:

main...ashione:feat/task-plan-lifecycle-hooks

Motivation

Codex has hooks for tools and session events, but there is not currently a hook surface for the higher-level lifecycle that external harnesses usually care about:

  • a task/turn has started
  • a task/turn has completed
  • a checklist plan was created
  • a checklist plan changed
  • a checklist plan reached completion

For human users, the checklist is visible progress. For an external harness, it can also be a machine-readable progress contract. Without lifecycle hooks or a bounded plan update API, a harness has to keep a separate out-of-band plan, inject synthetic text, or ask the model to rewrite plan state even when the change came from deterministic external state.

The use case I am trying to support is an orchestrator around Codex that can:

  • observe when a real task starts and finishes
  • capture task completion metadata
  • observe checklist creation/update/completion
  • append or update active checklist items from outside the model loop
  • avoid rewriting completed checklist history

Proposed Events

The prototype adds these command-hook events:

  • TaskCreated
  • TaskCompleted
  • PlanCreated
  • PlanUpdated
  • PlanCompleted

Task hook matchers use task_kind:

  • regular
  • review
  • compact

Plan hook matchers use plan_source:

  • update_plan
  • external
  • proposed_plan

TaskCompleted includes metadata such as the last agent message, completion timestamp, duration, and time to first token.

Plan hook payloads include the current plan, previous plan, explanation, and status counts. For Plan Mode proposed plans, the proposal text is exposed as plan_text, and it is intentionally not written into the checklist snapshot.

External Harness-Driven Plan Updates

The prototype adds an app-server JSON-RPC method:

turn/plan_update

The API lets an external client patch the active turn checklist in a restricted way. It requires the active turn id to match expectedTurnId, and supports two operations:

{ type: "append", step: string, status?: "pending" | "in_progress" | "completed" }
{ type: "update", index: number, step?: string, status?: "pending" | "in_progress" | "completed" }

The important constraint is that completed checklist items cannot be modified. My intent is to treat completed steps as historical facts, while still letting an external harness add new work or adjust open items.

A successful external update reuses the normal plan update notification path and emits plan lifecycle hooks with plan_source = "external".

Behavior Boundaries

The prototype keeps this intentionally narrow:

  • lifecycle hooks are command hooks only
  • hook failures are reported but do not block task completion or plan updates
  • external plan updates only affect the current active turn
  • external updates are patch-based, not full snapshot replacement
  • Plan Mode proposals remain text proposals and do not become checklist state

If blocking policy is needed later, I think it should probably be a separate pre-update hook such as PrePlanUpdate, rather than overloading these lifecycle notifications.

Prototype Status

The fork branch wires this through hook config, hook discovery/listing, app-server schema generation, task/plan lifecycle emission, turn-scoped plan state, and TUI hook labels.

I also did local validation, including hook tests, core plan-update tests, schema fixture checks, a release codex-cli build, and CLI smoke tests confirming that the new command hooks fire with the expected payloads.

I did hit some local environment limitations: just was not installed on my machine, and a full core lib run hit an unrelated existing stack overflow test. The release CLI build succeeded and was used for the hook verification.

Questions

Would the Codex team be open to this lifecycle hook surface and external harness-driven plan update API?

If the direction seems reasonable, I would appreciate guidance on:

  • event naming
  • payload shape
  • matcher semantics
  • whether turn/plan_update should stay app-server v2 only
  • whether the external patch constraints are strict enough
  • whether non-blocking lifecycle hooks are the right v1 behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    CLIIssues related to the Codex CLIapp-serverIssues involving app server protocol or interfacesenhancementNew feature or requesthooksIssues related to event hooksplanIssues involving plan mode

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions