feat: skills/agents detection from user prompts#126
Conversation
| 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) |
There was a problem hiding this comment.
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!
| # 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'), | ||
| }, | ||
| }) |
There was a problem hiding this comment.
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!
| if log.get('event', {}).get('hook_event_name') | ||
| in ('subagentStart', 'subagentStop') |
There was a problem hiding this comment.
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.
| if log.get('event', {}).get('hook_event_name') | |
| in ('subagentStart', 'subagentStop') | |
| if log.get('event', {}).get('hook_event_name') | |
| in ('subagentStop',) |
| 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) | ||
| ] |
There was a problem hiding this comment.
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!
| # 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'), | ||
| }, | ||
| }) |
There was a problem hiding this comment.
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.
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/SubagentStophook 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/hooks/unbound.py,codex/hooks/setup.py): Registers two new hook types and adds a second scan of the audit-log array inprocess_stop_eventto collectSubagentStopevents for the current session that occurred after the user prompt, appending them asTasktool_uses to the exchange. AllsuppressOutput: trueresponses are also replaced with plain{}to align with Codex platform constraints.cursor/unbound.py,cursor/hooks.json): Registers the hooks, adds asubagentStopbranch inbuild_llm_exchange, and gathers cross-generation subagent events inprocess_stop_eventusing a timestamp filter before merging them into the events list. The conditionif assistant_response or assistant_tool_usesis 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
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)Reviews (1): Last reviewed commit: "feat: skills/agents detection from user ..." | Re-trigger Greptile