Skip to content

feat: skills/agents detection from user prompts#126

Open
MohamedAklamaash wants to merge 1 commit into
stagingfrom
web-4507
Open

feat: skills/agents detection from user prompts#126
MohamedAklamaash wants to merge 1 commit into
stagingfrom
web-4507

Conversation

@MohamedAklamaash

@MohamedAklamaash MohamedAklamaash commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds detection and forwarding of subagent (agent-within-agent) invocations to the Unbound gateway for both Codex and Cursor hook integrations. It registers SubagentStart/SubagentStop hook events in the respective configuration files and folds the recovered subagent tool-use records into the LLM exchange payload that is sent after each turn.

  • Codex (codex/hooks/unbound.py, codex/hooks/setup.py): Registers two new hook types and adds a second scan of the audit-log array in process_stop_event to collect SubagentStop events for the current session that occurred after the user prompt, appending them as Task tool_uses to the exchange. All suppressOutput: true responses are also replaced with plain {} to align with Codex platform constraints.
  • Cursor (cursor/unbound.py, cursor/hooks.json): Registers the hooks, adds a subagentStop branch in build_llm_exchange, and gathers cross-generation subagent events in process_stop_event using a timestamp filter before merging them into the events list. The condition if assistant_response or assistant_tool_uses is widened so tool-use-only turns (no text response) are no longer silently dropped.

Confidence Score: 3/5

The core logic is reasonably sound, but both new subagent paths ship without any metrics or structured logging, and the Cursor path collects subagentStart events that are silently discarded by build_llm_exchange.

Two independent flows (Codex and Cursor) both introduce subagent event forwarding with zero instrumentation. If the timestamp filter over-filters or the event fields come back None, the gateway receives malformed or empty subagent data with no observable signal. The subagentStart collection in cursor/unbound.py is dead weight that adds noise to every exchange build without contributing anything.

cursor/unbound.py and codex/hooks/unbound.py — specifically the new subagent event gathering and forwarding paths in each.

Important Files Changed

Filename Overview
cursor/unbound.py Adds subagentStop event handling in build_llm_exchange and a cross-generation subagent event gather in process_stop_event. subagentStart events are collected but silently dropped by build_llm_exchange; the new flow has no metrics or structured logging.
codex/hooks/unbound.py Adds SubagentStop scanning loop in process_stop_event and replaces all suppressOutput responses with plain {}. No metrics or logging on the new subagent path; None values from missing event fields are silently forwarded.
codex/hooks/setup.py Registers SubagentStart and SubagentStop hook events in the Codex configuration, mirroring the existing hook pattern with a 60-second timeout.
cursor/hooks.json Adds subagentStart and subagentStop hook registrations pointing to the existing unbound.py script.

Sequence Diagram

sequenceDiagram
    participant User
    participant CursorHook as cursor/unbound.py
    participant SubagentHook as subagentStop hook
    participant AuditLog as Audit Log
    participant Gateway as Unbound Gateway

    User->>CursorHook: beforeSubmitPrompt
    CursorHook->>AuditLog: append log entry
    Note over CursorHook: Agent runs, spawns subagents
    SubagentHook->>CursorHook: subagentStop event
    CursorHook->>AuditLog: append subagent log entry
    CursorHook->>CursorHook: stop event fires
    CursorHook->>AuditLog: load_existing_logs()
    CursorHook->>CursorHook: group_events_by_generation()
    CursorHook->>CursorHook: "gather subagent_events (timestamp >= turn_start)"
    CursorHook->>CursorHook: build_llm_exchange(events + subagent_events)
    Note over CursorHook: subagentStop → Task tool_use appended
    CursorHook->>Gateway: POST /v1/hooks/cursor (exchange with subagent tool_uses)
Loading

Reviews (1): Last reviewed commit: "feat: skills/agents detection from user ..." | Re-trigger Greptile

Greptile also left 5 inline comments on this PR.

@MohamedAklamaash MohamedAklamaash requested a review from a team June 10, 2026 13:28
Comment thread cursor/unbound.py
Comment on lines +1177 to +1185
subagent_events = [
log
for gen_events in generations.values()
for log in gen_events
if log.get('event', {}).get('hook_event_name')
in ('subagentStart', 'subagentStop')
and (not turn_start or log.get('timestamp', '') >= turn_start)
]
exchange = build_llm_exchange(events + subagent_events, api_key)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing metrics for new subagent flow

The new subagent detection and forwarding path has zero instrumentation. Per the observability requirements, every new flow must ship a latency histogram and success/failure counters at minimum. For this specific flow, useful metrics would be: a counter for subagent_events_forwarded_total labelled by subagent_type, and a counter for subagent_events_skipped_total (e.g. filtered by timestamp) so failures in this logic are visible in production without needing to dig through logs. Without them, there is no way to tell how often subagent events are being found and forwarded, or whether the timestamp filter is silently dropping valid events.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread codex/hooks/unbound.py
Comment on lines +1086 to +1106
# Fold in subagent invocations from SubagentStop hook events. Codex subagents
# run in their own transcript and don't appear as function_calls in the parent
# rollout, so we recover them from the accumulated hook events (same session,
# after the user prompt) and forward them as Task tool_uses for the gateway.
for log in logs:
log_session_id = log.get('session_id') or log.get('event', {}).get('session_id')
if log_session_id != session_id:
continue
log_event = log.get('event', {}) if 'event' in log else log
if log_event.get('hook_event_name') != 'SubagentStop':
continue
if user_prompt_timestamp and log.get('timestamp') and log.get('timestamp') <= user_prompt_timestamp:
continue
assistant_tool_uses.append({
'type': 'SubagentStop',
'tool_name': 'Task',
'tool_input': {'subagent_type': log_event.get('agent_type')},
'tool_response': {
'last_assistant_message': log_event.get('last_assistant_message'),
},
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Missing metrics for new subagent flow

Same observability gap as in cursor/unbound.py: the new SubagentStop scanning loop has no Prometheus counters or histograms. Suggested additions: subagent_stop_events_forwarded_total (labelled subagent_type) to track which subagent types are active, and a subagent_stop_events_skipped_total counter when the timestamp filter drops an event. Without these, there is no signal to distinguish "subagents not running" from "subagent events being silently filtered out".

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread cursor/unbound.py
Comment on lines +1181 to +1182
if log.get('event', {}).get('hook_event_name')
in ('subagentStart', 'subagentStop')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 subagentStart events are included here but build_llm_exchange has no handler for them — they will silently fall through without being processed. If only completed invocations are needed, filter to subagentStop only.

Suggested change
if log.get('event', {}).get('hook_event_name')
in ('subagentStart', 'subagentStop')
if log.get('event', {}).get('hook_event_name')
in ('subagentStop',)

Comment thread cursor/unbound.py
Comment on lines +1176 to +1184
turn_start = min((l.get('timestamp', '') for l in events), default='')
subagent_events = [
log
for gen_events in generations.values()
for log in gen_events
if log.get('event', {}).get('hook_event_name')
in ('subagentStart', 'subagentStop')
and (not turn_start or log.get('timestamp', '') >= turn_start)
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No structured logging on the new subagent detection path

When subagent_events is populated (or empty), there is no log line recording what was found. If the gateway ever receives an exchange with missing or wrong subagent tool_uses, there is no way to reconstruct whether: (a) no subagent events existed in the log at all, (b) the timestamp filter dropped them, or (c) they were from a different generation. A single structured log at debug/info level—recording generation_id, len(subagent_events), and turn_start—would make this path debuggable from logs alone.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread codex/hooks/unbound.py
Comment on lines +1086 to +1106
# Fold in subagent invocations from SubagentStop hook events. Codex subagents
# run in their own transcript and don't appear as function_calls in the parent
# rollout, so we recover them from the accumulated hook events (same session,
# after the user prompt) and forward them as Task tool_uses for the gateway.
for log in logs:
log_session_id = log.get('session_id') or log.get('event', {}).get('session_id')
if log_session_id != session_id:
continue
log_event = log.get('event', {}) if 'event' in log else log
if log_event.get('hook_event_name') != 'SubagentStop':
continue
if user_prompt_timestamp and log.get('timestamp') and log.get('timestamp') <= user_prompt_timestamp:
continue
assistant_tool_uses.append({
'type': 'SubagentStop',
'tool_name': 'Task',
'tool_input': {'subagent_type': log_event.get('agent_type')},
'tool_response': {
'last_assistant_message': log_event.get('last_assistant_message'),
},
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 No structured logging on the new SubagentStop scanning path

The new loop that scans all logs for SubagentStop events has no log output. If the subagent_type or last_assistant_message fields are missing in a real event (e.g. log_event.get('agent_type') returns None), the resulting tool_use will be silently malformed and forwarded to the gateway with None values. A log line recording session_id, the number of events appended, and which agent_type values were found would allow failures to be diagnosed from logs without reproducing the environment.

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.

1 participant