Terminal Agent Notifications: Make AI Coding Agents Proactively Report Progress
This article explains why repeatedly checking a terminal AI coding agent is an inefficient waiting pattern, and how to replace manual polling with event-driven notifications. It presents a five-layer architecture for collecting agent lifecycle events through CLI hooks, normalizing them into a unified event model, routing them to the correct terminal session, aggregating state updates, and sending actionable notifications. The article also covers approval feedback loops, reliability constraints, data boundaries, and metrics for evaluating notification quality.
Terminal Agent Notifications: Make AI Coding Agents Proactively Report Progress
1. The Problem: Watching a Screen Is an Expensive Way to Wait
When a refactoring task is handed to an AI coding agent running in a terminal, the timeline often looks like this:
- The task is submitted.
- The terminal starts printing rolling logs.
- The developer decides, “This will take a while,” and switches to something else.
- A few minutes later, the developer returns and discovers that the workflow stopped at a point requiring human authorization.
- The machine has already been idle for several minutes.
If multiple terminal panes are running agents at the same time, the problem gets worse. The developer no longer knows which pane to watch. The result is a mechanical loop: switch back to the terminal every few dozen seconds, scan the screen, then switch away again.
What is consumed here is not machine time, but human attention bandwidth.
A typical agent task has three moments that require human involvement:
- Task start
- Approval request
- Task completion, either success or failure
The interval between the first two moments and the final one may last several minutes. However, the developer’s judgment cycle for “is the task still running?” is usually only a few dozen seconds. This mismatch causes many ineffective checks.
Consider a simple example. Suppose a refactoring task takes 8 minutes. For 6 of those minutes, it runs normally without needing human attention. If the developer glances at the screen every 40 seconds, there will be roughly 9 manual checks. About 8 of them carry almost no useful information, yet each check involves switching context and rebuilding mental state.
Context switching has a fixed cost:
- Reload the screen context
- Recover the previous line of thought
- Decide what to do next
The only truly valuable piece of information is:
It is your turn now.
2. Root Cause: Solving a Push Problem With a Pull Model
This inefficiency is not caused by the tool’s capabilities. It comes from a mismatch in interaction models.
Repeatedly checking an agent’s task status is a classic pull model:
- A human acts as the poller.
- The human actively samples system state.
But agent state changes are discrete events. They are naturally suited to a push model:
- When state transitions occur, the system notifies subscribers.
The cost structures of the two models are fundamentally different:
- Pull model cost grows with waiting time, and most samples provide little value.
- Push model cost grows only with the number of events, which is usually a single-digit number.
Therefore, the correct goal is:
Move humans out of the polling loop and only involve them when an event wakes them up.
This leads to three design constraints:
- Events must be captured.
- Events must be translated into unified semantics.
- Events must be accurately attributed to the correct terminal tab, pane, and session.
Otherwise, notifications degrade into noise.
3. Goals and Boundaries
The design goals can be reduced to four points:
Interruptibility
When a task enters a state requiring human action, the developer receives a clear notification regardless of whether the terminal is in the background or foreground.Low noise
Normal running-state changes should not interrupt the developer. Only decision points deserve interruption.Attribution
Every notification should allow one-click navigation back to the source: tab, pane, and session.Agent-count independence
Adding a new AI coding CLI should not require changes to the notification layer or the display layer.
The non-goals are equally important:
- Do not try to understand the semantic content of what the agent is doing. That belongs to log analysis.
- Do not make decisions on behalf of the agent.
- Do not introduce blocking dependencies into the agent execution path.
4. Overall Architecture: A Five-Layer Pipeline
The architecture can be divided into five layers:
Agent process│ 1. Lifecycle callbacks (external command + event JSON)▼Collection adapter layer ── CLI-specific hook differences end here│ 2. Unified event (Envelope)▼Event normalization layer ── validation / deduplication / ordering / context enrichment│ 3. Attributed event▼Attribution routing layer ── session ↔ tab ↔ pane mapping table│ 4. Aggregated state view▼Notification and interaction layer ── system notifications / compact status bar / result cards / approval feedback
The key design principle is:
Keep differences at the top of the pipeline; accumulate capabilities downstream.
Different CLI tools expose different callback mechanisms:
- Callback timing
- Input format
- Field naming
- Whether a session identifier is included
These differences should be absorbed once in the collection adapter layer. Everything downstream should receive a stable, unified data structure.
5. Layer One: Hook Collection
Many mainstream terminal AI coding tools provide a hook mechanism. At lifecycle points such as:
- Session start
- Permission confirmation request
- External command execution
- Normal termination
- Abnormal exit
the CLI executes a user-configured external command and passes event information through standard input, usually as structured JSON.
This means collection can be very thin. A minimal collection script needs only three steps:
- Read standard input.
- Add context.
- Forward to the local event pipeline.
#!/usr/bin/env bash# ~/.hooks/agent-event.sh - triggered by agent lifecycle callbackspayload="$(cat)" # 1. Raw event JSONctx="$(current_session_context)" # 2. Inject attribution information (see Section 6)printf '%s\n%s\n' "$ctx" "$payload" \| nc -U "$LOCAL_EVENT_SOCKET" # 3. Send to local Unix Domain Socket
A few engineering notes:
Callbacks must be asynchronous and non-blocking.
If the collection script is slow or fails, it must not affect the agent’s main flow. All delivery actions should use timeouts and silent failure.Use only Unix Domain Sockets or local named pipes.
Do not open an additional network listening port for this purpose. This avoids expanding the local attack surface.Fallbacks are allowed, but confidence must be marked.
If a CLI does not provide hooks, possible fallbacks include:- Wrapping the startup script and monitoring the process exit code.
- Conservatively matching terminal output patterns.
Events from these sources should be explicitly marked as lower confidence than native hooks.
6. Layer Two: Unified Event Model
The event model is the core contract of the entire mechanism.
A recommended structure is:
Envelope + Payload
- The envelope carries common fields required by all downstream consumers.
- The payload carries information specific to the event type.
Example:
{"schema": "agent-event/v1","event_id": "01J9X-...","session_id": "sess-7f3a","seq": 42,"occurred_at": "2026-08-26T16:56:26.625Z","source": { "adapter": "cli-a", "version": "1.2.3" },"event_type": "approval_requested","severity": "action_required","payload": {"title": "Approval required to run command","summary": "git push --force origin main","request_id": "req-88c1"}}
Event types can be reduced to four categories, which are sufficient for decision-making:
| Event type | Meaning | Requires human action? |
|---|---|---|
session_started |
Session created; task enters execution | No |
progress_tick |
Optional periodic heartbeat | No, only used to detect liveness |
approval_requested |
Blocked waiting for human authorization | Yes |
session_ended |
Ended, with success/failure and summary | Only on failure |
Three fields are easy to overlook:
event_id+seq
Used for idempotent deduplication and ordering recovery. Hook delivery may be duplicated due to timeout retries, so the terminal side must be able to recognize duplicates.occurred_atandreceived_at
These should be kept separate.occurred_atis used for ordering and latency measurement.received_atis used for troubleshooting the delivery pipeline.severity
Onlyaction_requiredshould trigger interruptive notifications, such as sound or banner notifications. Other events should be downgraded to silent records.
The normalization layer should also perform a translation task: compress each CLI’s informal output into a one-line summary.
Summary generation should:
- Limit length.
- Strip sensitive path fragments.
- For failures, include the failure stage and error category so the developer can quickly judge whether immediate action is needed.
7. Layer Three: Attribution Routing
Notifications are useful only if they can jump back to the correct location.
Attribution information should not be guessed. It should be injected as environment variables by the terminal when the agent process starts, and then carried back through hook events:
# The terminal injects its own coordinates when launching each paneexport AGENT_TAB_ID="tab-3"export AGENT_PANE_ID="pane-3b"export AGENT_WORKSPACE="$PWD"export AGENT_SESSION_ID="sess-7f3a"
The attribution routing layer maintains a mapping table:
session_id → (workspace, tab_id, pane_id)
Unread counts should be:
- Accumulated independently per pane.
- Aggregated into a single indicator at the tab level.
This directly affects usability when multiple agents run in parallel. If the developer sees a dot on the tab, they know that something in that group needs attention. Clicking into it reveals:
- Which pane
- Which task
- What summary
Liveness detection also belongs to this layer.
If a session has no progress_tick or output activity for a long time, it should be marked as “possibly lost” instead of continuing to display “working.”
A silent hang is one of the hardest failure types to discover.
8. Layer Four: State Aggregation and Notification Policy
With an event stream in place, the next step is to suppress noise.
Each session can be represented by a small state machine:
started│▼┌─────────┐ approval_requested ┌──────────────┐│ WORKING │ ───────────────────► │ WAIT_APPROVAL│└─────────┘ ◄─────────────────── └──────────────┘│ approved/rejected ││ completed / failed │▼ ▼┌──────┐ ┌──────┐│ DONE │ │ DONE │└──────┘ └──────┘
Several practical aggregation rules are recommended:
Debounce
Events from the same session within 2 seconds are merged for display. Only the event with the highestseverityis kept.Throttle
progress_tickevents are aggregated into one “working” status update every 5 to 10 seconds. They should not produce per-event notifications.Status bar vs. notification
Non-decision events should only update a compact status bar, such as:- Working
- Completed ×3
Only
action_requiredevents should use interruptive notifications.Result cards
End events generate a result entry with a summary.
Failure entries are pinned to the top and include an entry point to expand the original logs.
9. Layer Five: Approval Feedback Loop
Read-only notifications solve only “when to look.”
To truly eliminate interruptions, the developer should be able to make decisions directly from the notification.
This requires approval actions to be sent back to the agent process that is blocked waiting for standard input.
There are usually two implementation paths:
Original terminal channel
The notification UI writes the decision into the corresponding session’s control channel. This is equivalent to pressing the confirmation key inside the terminal.
Advantage: agent behavior is unchanged.
Disadvantage: requires tight integration with terminal reuse capabilities.Proxy startup channel
A lightweight proxy launches the agent and forwards input to it.
Advantage: more flexible.
Disadvantage: introduces an extra process and an additional failure point.
Regardless of the chosen path, three edge cases must be handled:
1. Approval timeout: default to rejection and record the reason to avoid a session hanging forever.2. Concurrent approvals: when multiple sessions request approval at the same time, display them in request order; batch blind approval is prohibited.3. Channel unavailable: if feedback fails, immediately mark the notification as “Please return to the terminal and handle manually.”
It is important to emphasize:
Default rejection is safer than keeping the approval waiting indefinitely.
A forgotten approval request can occupy a session and resources for a long time. Rejection is reversible: a human can always start the operation again.
10. Reliability and Data Boundaries
The collection pipeline sits between the agent and the developer. Any design flaw affects both sides. Several hard constraints are required:
Failure must not block the main flow.
If the collection script, event service, or notification layer fails, the agent must continue running normally. The entire pipeline should follow a best-effort delivery and silent degradation model.Local-first by default.
Event data should stay within local inter-process communication by default. No external sending is performed unless explicitly enabled. If cross-device viewing is required, it must be explicitly enabled with transport encryption and access control.Data minimization.
Only event metadata and necessary summaries should be collected. Do not upload complete code, secrets, environment variables, or full command output. Before summary generation, suspected credential content should be redacted.Least privilege.
Notification and approval channels should be bound to the current user session. Arbitrary local processes should not be able to trigger forged “completed” or “approved” events. Event sources should be verified using process credentials, not solely by trusting the message content.Observability.
Instrument the event pipeline itself. Record delivery latency, dropped events, and retry counts. Otherwise, it becomes impossible to distinguish “the agent did not emit an event” from “the event was not processed.”
11. How to Measure Effectiveness
Whether the change works should be validated with metrics, not by feel.
Four metrics are recommended:
| Metric | Definition | Expected direction |
|---|---|---|
| Decision discovery latency | Time from approval_requested to human wake-up |
From minutes to seconds |
| Idle time | Percentage of total time spent in WAIT_APPROVAL |
Significantly lower |
| Manual switch count | Number of times the developer manually switches back to the terminal per unit time | Close to 0 |
| Notification effectiveness | Percentage of interruptive notifications that truly require action | Higher is better |
“Notification effectiveness” is the most easily ignored metric.
If the implementation is rough, notifications will be polluted by a large number of heartbeat events. Developers will quickly learn to ignore them.
A notification system with a high false-positive rate has almost zero real value.
12. Rollout Steps
Confirm the lifecycle callback mechanism of the AI agent CLI being used, including input fields.
Start by connecting onlysession_startedandsession_endedto get the pipeline working end to end.Define the event envelope and event type enum, including a version number.
This becomes the contract for future expansion.Implement the local event service.
A single-file Unix Domain Socket is enough.
Complete deduplication, ordering, and attribution mapping.Connect native system notifications and one always-on compact status bar.
First support four states:- Working
- Completed
- Failed
- Waiting for approval
Connect approval feedback.
Make sure timeout default rejection and failure marking are implemented.Add adapters.
For each new CLI, add only one collection script and one field-mapping layer.
Downstream components should require zero changes.Add metrics collection.
Use one week of real task data to calibrate debounce and throttle parameters.
13. Evolution Directions
Once the single-machine solution is working, there are three natural expansion paths:
Remote and containerized development environments
When the event source and display client are not on the same machine, a reliable event transport channel is required. Message queues or long-lived push connections can be reused. Attention should be paid to remote event latency jitter and reconnection compensation.Team-level views
Event metadata can be aggregated into centralized logging and monitoring systems.
Teams can observe questions such as:- Average approval waiting time
- Which repositories and stages fail most often
These aggregated views are often more valuable than individual notifications for improving development workflows.
Finer-grained task semantics
Add structured task results above the event layer, such as:- Modified file scope
- Whether tests passed
- Duration distribution
This allows completion notifications to be more than just “completed.” They can directly indicate whether the next step is safe to proceed.
14. Summary
The essence of the problem is an interaction-model mismatch.
Task state is a set of discrete events, but humans perform periodic sampling.
By unifying the collection, normalization, attribution, and proactive delivery of agent lifecycle events, and by using aggregation policies to control noise and approval feedback to eliminate interruptions, developers can shift from:
Glancing at the screen every few dozen seconds
to:
Being woken up only when it is truly their turn.
This mechanism does not depend on any proprietary capability from a specific tool. Any AI coding CLI that provides lifecycle callbacks can integrate with it.
Its value is not in the notification itself, but in returning fragmented time to work that requires deep thinking.
