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:

    1. The task is submitted.
    2. The terminal starts printing rolling logs.
    3. The developer decides, “This will take a while,” and switches to something else.
    4. A few minutes later, the developer returns and discovers that the workflow stopped at a point requiring human authorization.
    5. 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:

    1. Events must be captured.
    2. Events must be translated into unified semantics.
    3. 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:

    1. 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.

    2. Low noise
      Normal running-state changes should not interrupt the developer. Only decision points deserve interruption.

    3. Attribution
      Every notification should allow one-click navigation back to the source: tab, pane, and session.

    4. 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:

    1. Agent process
    2. │ 1. Lifecycle callbacks (external command + event JSON)
    3. ▼
    4. Collection adapter layer ── CLI-specific hook differences end here
    5. │ 2. Unified event (Envelope)
    6. ▼
    7. Event normalization layer ── validation / deduplication / ordering / context enrichment
    8. │ 3. Attributed event
    9. ▼
    10. Attribution routing layer ── session ↔ tab ↔ pane mapping table
    11. │ 4. Aggregated state view
    12. ▼
    13. 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:

    1. Read standard input.
    2. Add context.
    3. Forward to the local event pipeline.
    1. #!/usr/bin/env bash
    2. # ~/.hooks/agent-event.sh - triggered by agent lifecycle callbacks
    3. payload="$(cat)" # 1. Raw event JSON
    4. ctx="$(current_session_context)" # 2. Inject attribution information (see Section 6)
    5. printf '%s\n%s\n' "$ctx" "$payload" \
    6. | 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:

    1. {
    2. "schema": "agent-event/v1",
    3. "event_id": "01J9X-...",
    4. "session_id": "sess-7f3a",
    5. "seq": 42,
    6. "occurred_at": "2026-08-26T16:56:26.625Z",
    7. "source": { "adapter": "cli-a", "version": "1.2.3" },
    8. "event_type": "approval_requested",
    9. "severity": "action_required",
    10. "payload": {
    11. "title": "Approval required to run command",
    12. "summary": "git push --force origin main",
    13. "request_id": "req-88c1"
    14. }
    15. }

    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_at and received_at
      These should be kept separate.
      occurred_at is used for ordering and latency measurement.
      received_at is used for troubleshooting the delivery pipeline.

    • severity
      Only action_required should 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:

    1. # The terminal injects its own coordinates when launching each pane
    2. export AGENT_TAB_ID="tab-3"
    3. export AGENT_PANE_ID="pane-3b"
    4. export AGENT_WORKSPACE="$PWD"
    5. export AGENT_SESSION_ID="sess-7f3a"

    The attribution routing layer maintains a mapping table:

    1. 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:

    1. started
    2. │
    3. ▼
    4. ┌─────────┐ approval_requested ┌──────────────┐
    5. │ WORKING │ ───────────────────► │ WAIT_APPROVAL│
    6. └─────────┘ ◄─────────────────── └──────────────┘
    7. │ approved/rejected │
    8. │ completed / failed │
    9. ▼ ▼
    10. ┌──────┐ ┌──────┐
    11. │ DONE │ │ DONE │
    12. └──────┘ └──────┘

    Several practical aggregation rules are recommended:

    • Debounce
      Events from the same session within 2 seconds are merged for display. Only the event with the highest severity is kept.

    • Throttle
      progress_tick events 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_required events 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:

    1. 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.

    2. 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. 1. Approval timeout: default to rejection and record the reason to avoid a session hanging forever.
    2. 2. Concurrent approvals: when multiple sessions request approval at the same time, display them in request order; batch blind approval is prohibited.
    3. 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

    1. Confirm the lifecycle callback mechanism of the AI agent CLI being used, including input fields.
      Start by connecting only session_started and session_ended to get the pipeline working end to end.

    2. Define the event envelope and event type enum, including a version number.
      This becomes the contract for future expansion.

    3. Implement the local event service.
      A single-file Unix Domain Socket is enough.
      Complete deduplication, ordering, and attribution mapping.

    4. Connect native system notifications and one always-on compact status bar.
      First support four states:

      • Working
      • Completed
      • Failed
      • Waiting for approval
    5. Connect approval feedback.
      Make sure timeout default rejection and failure marking are implemented.

    6. Add adapters.
      For each new CLI, add only one collection script and one field-mapping layer.
      Downstream components should require zero changes.

    7. 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.