Skip to content

[Hooks] Schema changes only - AppServer protocol additions & Hooks I/O schema#13408

Closed
eternal-openai wants to merge 5 commits intomainfrom
eternal/hooks-schema-setup-1
Closed

[Hooks] Schema changes only - AppServer protocol additions & Hooks I/O schema#13408
eternal-openai wants to merge 5 commits intomainfrom
eternal/hooks-schema-setup-1

Conversation

@eternal-openai
Copy link
Copy Markdown
Contributor

@eternal-openai eternal-openai commented Mar 3, 2026

This is just the schema + in development feature flag set up from #13276 to make review a bit easier

Testing

  • no functionality changes
  • the codex apps should work as expected

Details

  • codex_hooks feature flag: introduces an under-development feature gate, off by default.
  • Rust-owned hook schemas: adds Rust-generated JSON Schema fixtures for hook command I/O (SessionStart and Stop) so hook contracts live in codex-rs
  • Core protocol hook events:
    • EventMsg::HookStarted: emitted when a hook run begins.
    • EventMsg::HookCompleted: emitted when a hook run finishes.
  • Core hook schema objects:
    • HookEventName: identifies which hook event ran.
    • HookHandlerType: identifies what kind of handler ran.
    • HookExecutionMode: describes how the handler was executed.
    • HookScope: describes where the hook applies.
    • HookRunStatus: captures the final outcome of the run.
    • HookOutputEntry: represents one normalized output row from a hook.
    • HookRunSummary: represents the full normalized, displayable, and persistable result of one hook run.
  • AppServer v2 protocol:
    • adds hook/started
    • adds hook/completed
    • adds Turn.hookRuns so completed hook runs can be attached to a turn
  • Thread history / persistence scaffolding: completed hook runs can now be threaded through rollout history reconstruction onto their owning turn.
  • Compile-only plumbing: adds minimal no-op handling in app-server, exec, mcp-server, core, and TUI so the new protocol surface builds cleanly on main.

@eternal-openai eternal-openai requested a review from bolinfest March 3, 2026 23:28
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 3, 2026


Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request. Posted by the CLA Assistant Lite bot.

@eternal-openai eternal-openai marked this pull request as ready for review March 3, 2026 23:34
@eternal-openai eternal-openai requested review from pakrym-oai and pkomlev and removed request for pkomlev March 3, 2026 23:35
Copy link
Copy Markdown
Collaborator

@jif-oai jif-oai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OOC, why fully dropping the previous hooks implementation (not drop here but mostly incompatible with what you're building here)

IMO the implementation direction was quite good

.send_server_notification(ServerNotification::ItemCompleted(notification))
.await;
}
EventMsg::HookStarted(_) => {}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why having them if we don't emit them?

This should be mapped to your hook/completed, ...

turn: Turn {
id: event_turn_id,
items: vec![],
hook_runs: vec![],
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh? This sounds strange to never populate this no?

| EventMsg::ShutdownComplete
| EventMsg::DeprecationNotice(_)
| EventMsg::ItemStarted(_)
| EventMsg::HookStarted(_)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to persist them? My guess would be that this is quite relevant

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Persistence is handled by HookRunSummary in the larger PR, so that only the summaries of what the hook returned are persisted

return;
}

self.ensure_turn().hook_runs.push(run);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This thing will check the current turn if there is one, otherwise create a mock turn in the history in order to push the run

But you don't seem to be filtering out non-turn based hooks such as "session start" for example. So why would we attach this one to a turn?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start session imo does have a turn attached, it's the first prompt that's sent when starting or resuming a session. I think the mechanics are simpler doing it this way with context additions

{
SchemaSettings::draft07()
.with(|settings| {
settings.option_add_null_type = false;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why do we have this but you also redefine your own NullableString? Someting feels a bit wrong here...

As a result you can see that your JSON schema can put null when an Option is None but schemars will actually not include them...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I had removed NullableString for now, since it would throw warnings in this version of the PR

#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "snake_case")]
pub enum HookRunStatus {
Running,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Running state is never used and I don't find any gates for race on subscription/execution + this is never reported to the user

@eternal-openai
Copy link
Copy Markdown
Contributor Author

Thanks @jif-oai for the review - to address why things aren't used - it was recommended that I split up #13276 into multiple parts in order to make review simpler. So this PR adds just the hook schema changes and overview of the protocol changes, not the actual functionality

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.

2 participants