Skip to content

What is the deterministic way to monitor a session is completed #3815

Closed

Description

@krishna1s

Question

Hi Team,

We are using the opencode/sdk and we are observing one issue, where we monitor a session based on session event, and we receive the session idle event sooner, but it seems session might still be working, is there is any deterministic way where we can say agent has stop working and might be asking for user input. Also let me know if it is possible to done by experimental hooks, please provide any sample code if there to check for session completetion

Thanks

Activity

  1. github-actions commented on Nov 3, 2025

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  2. rekram1-node commented on Nov 3, 2025

    @rekram1-node
    Contributor

    @krishna1s

    we receive the session idle event sooner

    Sooner than what? I could see you getting session idle event and then the title generation completing after and sending session updated, but maybe you are encountering some other issues?

  3. self-assigned this
    on Nov 3, 2025
  4. rekram1-node commented on Nov 7, 2025

    @rekram1-node
    Contributor

    Hm I guess the title generation is kinda a "background" thing so it doesnt follow the normal rules but I see what you are saying

  5. krishna1s commented on Nov 10, 2025

    @krishna1s
    Author

    @rekram1-node I am also observing the same , session idle is coming sooner the agent is completing work

    Also the issues which you have tagged, message api doesn't even guaranteed that agent has finished the worked, What I have observed is, when a session hang, message api is stuck forever so we never know when is the actual session getting completed

  6. rekram1-node commented on Nov 10, 2025

    @rekram1-node
    Contributor

    @krishna1s Did I tag an issue? I dont think I did?

    session idle is coming sooner the agent is completing work

    are you using subagents or something, can u provide any steps to reproduce this? The only case this should be possible is for one of the background things like title gen to still be going, otherwise we send that event after the agent loop is finished

    I have a plugin that fires toasts whenever the session idle happens, I've never seen it fire a toast before the session was complete but maybe there is some edge case that I don't encounter that you guys have?

  7. danrasmuson commented on Mar 24, 2026

    @danrasmuson

    So one workaround is that it is possible to query the internal opencode DB for the session in question and then get the last message and see if its last_finish == ‘stop’.

    opencode status --session ses_2e30ba9fbffe9wtQTgcG0aGZoQ | to json

    {
    "id": "ses_2e30ba9fbffe9wtQTgcG0aGZoQ",
    "title": "Autocomplete issue in drafts with nvim headless test",
    "slug": "mighty-mountain",
    "version": "1.3.0",
    "messages": 36,
    "status": "completed",
    "last_finish": "stop"
    }

    #!/usr/bin/env nu
    
    # OpenCode session utilities
    #
    # Query the local OpenCode SQLite database for session information.
    # The database lives at ~/.local/share/opencode/opencode.db.
    #
    # Commands:
    #   opencode status - Get the status of a session by ID
    #
    # Examples:
    #   opencode status --session ses_2e30ba9fbffe9wtQTgcG0aGZoQ
    
    # Get the status of an OpenCode session
    #
    # Queries the local SQLite database to determine whether a session is
    # completed, interrupted, or idle. Status is derived from the last
    # assistant message's finish reason and timing data.
    #
    # Statuses:
    #   completed    - Agent finished normally (last message finish = "stop")
    #   interrupted  - Agent was mid-tool-call and stopped (finish = "tool-calls")
    #   filtered     - Response was blocked by content filter
    #   in-progress  - Last assistant message has no completion time (still running)
    #   idle         - No assistant messages yet (user prompt with no response)
    #
    # Examples:
    #   opencode status --session ses_2e30ba9fbffe9wtQTgcG0aGZoQ
    #   opencode status --session ses_2e30ba9fbffe9wtQTgcG0aGZoQ | get status
    #
    # Returns: Record with session details and status
    export def "opencode status" [
      --session: string  # The session ID (e.g., ses_xxx)
    ]: nothing -> record {
      let db = $"($env.HOME)/.local/share/opencode/opencode.db"
    
      let raw = (
        sqlite3 -json $db $"
          SELECT
            s.id,
            s.title,
            s.slug,
            s.version,
            s.time_created,
            s.time_updated,
            \(SELECT count\(*\) FROM message WHERE session_id = s.id\) as message_count,
            \(
              SELECT json_extract\(m.data, '$.finish'\)
              FROM message m
              WHERE m.session_id = s.id
                AND json_extract\(m.data, '$.role'\) = 'assistant'
              ORDER BY m.time_created DESC
              LIMIT 1
            \) as last_finish,
            \(
              SELECT json_extract\(m.data, '$.time.completed'\)
              FROM message m
              WHERE m.session_id = s.id
                AND json_extract\(m.data, '$.role'\) = 'assistant'
              ORDER BY m.time_created DESC
              LIMIT 1
            \) as last_completed
          FROM session s
          WHERE s.id = '($session)';
        "
      )
    
      if ($raw | str trim | is-empty) {
        error make { msg: $"Session ($session) not found" }
      }
    
      let row = ($raw | from json | first)
    
      let status = if ($row.last_finish | is-empty) and ($row.last_completed | is-empty) {
        "idle"
      } else if ($row.last_completed | is-empty) {
        "in-progress"
      } else if $row.last_finish == "stop" {
        "completed"
      } else if $row.last_finish == "tool-calls" {
        "interrupted"
      } else if $row.last_finish == "content-filter" {
        "filtered"
      } else {
        $"unknown \(($row.last_finish)\)"
      }
    
      {
        id: $row.id
        title: $row.title
        slug: $row.slug
        version: $row.version
        messages: ($row.message_count | into int)
        status: $status
        last_finish: $row.last_finish
      }
    }
    
  8. rekram1-node commented on May 22, 2026

    @rekram1-node
    Contributor

    Agent-written answer for the V2 SDK path:

    For V2, the deterministic API is client.v2.session.wait(...). Queue the prompt, then await wait for that session. It returns only after the V2 session agent loop becomes idle.

    import { createOpencode } from "@opencode-ai/sdk/v2"
    
    const opencode = await createOpencode()
    const client = opencode.client
    
    const sessionID = "ses_..."
    
    await client.v2.session.prompt({
      sessionID,
      prompt: {
        text: "Do the task",
      },
    })
    
    await client.v2.session.wait({ sessionID })
    
    console.log("V2 agent loop is idle")

    If your integration also needs to know whether the agent is blocked waiting for user input, subscribe to events separately and handle permission.asked / question.asked while the wait is pending:

    import { createOpencode } from "@opencode-ai/sdk/v2"
    
    const opencode = await createOpencode()
    const client = opencode.client
    const sessionID = "ses_..."
    
    const events = await client.event.subscribe()
    const monitor = (async () => {
      for await (const event of events.stream) {
        if (event.type === "permission.asked" && event.properties.sessionID === sessionID) {
          console.log("waiting for permission", event.properties)
        }
    
        if (event.type === "question.asked" && event.properties.sessionID === sessionID) {
          console.log("waiting for an answer", event.properties)
        }
      }
    })()
    
    await client.v2.session.prompt({
      sessionID,
      prompt: {
        text: "Do the task",
      },
    })
    
    await client.v2.session.wait({ sessionID })
    await events.stream.return?.(undefined)
    await monitor

    So for V2, do not infer completion from session.idle; use client.v2.session.wait({ sessionID }). Use the event stream only for progress/user-input notifications.

  9. bilogic commented on Jun 30, 2026

    @bilogic

    @rekram1-node

    is that a plugin? My plugin is trying to determine if a session has "completed"

  10. bilogic commented on Jul 2, 2026

    @bilogic

    @rekram1-node

    Started and Ended are sent almost together, any idea why the session.wait doesn't hold back the Ended? Thanks!

    import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
    
    const pending = new Map();
    
    async function waitForCompletion(client, sessionID, promptText) {
      try {
        client.v2.session.wait({ sessionID });
        console.log(`Ended: ${promptText}`);
      } catch (err) {
        console.error("[session-plugin] wait error:", err);
      } finally {
        pending.delete(sessionID);
      }
    }
    
    export default async function sessionPlugin(input) {
      const v2Client = createOpencodeClient({
        baseUrl: input.serverUrl.toString(),
      });
    
      return {
        "chat.message": async (msgInput, output) => {
          const { sessionID } = msgInput;
          const parts = output.parts ?? [];
          const text = parts.map((p) => (p.type === "text" ? p.text : "")).filter(Boolean).join("\n");
          if (!text) return;
    
          const prev = pending.get(sessionID);
          if (prev) {
            pending.set(sessionID, `${prev}\n${text}`);
            return;
          }
    
          console.log(`Started: ${text}`);
    
          pending.set(sessionID, text);
          waitForCompletion(v2Client, sessionID, text);
        },
      };
    }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions