Skip to content

[FEATURE]: Support subagent information in ACP #11576

Closed

Description

@VandeeFeng

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

As far as I explored: the loadSessionMode method in OpenCode on the server side will filter out subagents in latest opencode ACP.

And I can't find a easy way to get the information of subagents directly through ACP.

It's not convenient for project based on ACP such as xenodium/agent-shell in Emacs. I can't @ specific subagent through ACP.

Here is an implementation of @ a specific subagent with agent-shell in Emacs, illustrating how inconvenient it is.

I'm not a technical guy, but I love opencde and Emacs.

Hope this little tip can help others using opencode in Emacs.

;;; agent-shell OpenCode Subagent Support
;;; Shows available subagents and enables @@subagent invocation syntax
(defvar agent-shell-opencode--subagents nil
  "Cached list of available subagents.")

(defun agent-shell-opencode--get-subagent-description (agent-name)
  "Get description for subagent AGENT-NAME from its config file."
  (let* ((config-file (expand-file-name (format "%s.md" agent-name)
                                        "~/.config/opencode/agents/"))
         (description
          (when (file-exists-p config-file)
            (with-temp-buffer
              (insert-file-contents config-file)
              (goto-char (point-min))
              (when (re-search-forward "^description: \\(.*\\)$" nil t)
                (match-string 1))))))
    (or description "Subagent")))

(defun agent-shell-opencode--list-subagents ()
  "List all subagents using opencode agent list command.
Returns list of agent alists with :id, :name, :description."
  (let* ((output (shell-command-to-string "opencode agent list"))
         (lines (split-string output "\n"))
         agents)
    (dolist (line lines)
      (when (string-match "^\\([^\s]+\\) (subagent)$" line)
        (let* ((name (match-string 1 line))
               (description (agent-shell-opencode--get-subagent-description name)))
          (push `((:id . ,name)
                  (:name . ,name)
                  (:description . ,description))
                agents))))
    (nreverse agents)))

(defun agent-shell-opencode--format-subagents (subagents)
  "Format SUBAGENTS list for display."
  (mapconcat
   (lambda (agent)
     (let ((name (map-elt agent :name))
           (desc (map-elt agent :description)))
       (format "@@%s - %s" (propertize name 'font-lock-face 'font-lock-keyword-face) desc)))
   subagents
   "\n"))

(defun agent-shell-opencode--show-subagents-fragment ()
  "Add subagents UI fragment after available modes fragment."
  (when-let ((subagents agent-shell-opencode--subagents)
             ((> (length subagents) 0))
             ((fboundp 'agent-shell--update-fragment))
             (state agent-shell--state)
             (request-count (map-elt state :request-count))
             (buffer (map-elt state :buffer)))
    (with-current-buffer buffer
      (save-excursion
        (goto-char (point-max))
        (when (text-property-search-backward
               'agent-shell-ui-state nil
               (lambda (_ prop-state)
                 (equal (map-elt prop-state :qualified-id)
                        (format "%s-available_modes" request-count)))
               t)
          (let* ((block-range (agent-shell-ui--block-range :position (point)))
                 (insert-pos (map-elt block-range :end)))
            (goto-char insert-pos)
            (let ((inhibit-read-only t))
              (insert "\n\n")
              (agent-shell-ui--insert-fragment
               (agent-shell-ui-make-fragment-model
                :namespace-id (number-to-string request-count)
                :block-id "available_subagents"
                :label-left (propertize "Available subagents" 'font-lock-face 'font-lock-doc-markup-face)
                :body (agent-shell-opencode--format-subagents subagents))
               (format "%s-available_subagents" request-count)))))))))

(defun agent-shell-opencode--load-subagents-delayed ()
  "Load subagents and show them in UI after delay."
  (run-with-timer
   0.5 nil
   (lambda ()
     (when-let ((subagents (agent-shell-opencode--list-subagents))
                ((> (length subagents) 0)))
       (setq agent-shell-opencode--subagents subagents)
       (agent-shell-opencode--show-subagents-fragment)))))

(defun agent-shell-opencode--around-initiate-session (orig-fun &rest args)
  "Around advice for agent-shell--initiate-session."
  (let* ((plist args)
         (on-session-init (plist-get plist :on-session-init))
         (new-on-session-init
          (lambda ()
            (funcall on-session-init)
            (agent-shell-opencode--load-subagents-delayed))))
    (plist-put plist :on-session-init new-on-session-init)
    (apply orig-fun plist)))

(advice-add 'agent-shell--initiate-session :around
            #'agent-shell-opencode--around-initiate-session)

;;; Transform @@subagent syntax to @subagent before sending to OpenCode
(defun agent-shell-opencode--filter-send-command-args (args)
  "Transform @@subagent to @subagent in prompt before sending."
  (let ((prompt (plist-get args :prompt)))
    (when (and prompt (stringp prompt))
      (plist-put args :prompt (replace-regexp-in-string "@@" "@" prompt))))
  args)

(advice-add 'agent-shell--send-command :filter-args
            #'agent-shell-opencode--filter-send-command-args)

;;; @@subagent completion support
(defun agent-shell-opencode--subagent-completion-at-point ()
  "Complete subagent names after @@ prefix."
  (when-let* ((bounds (agent-shell--completion-bounds "[:alnum:]_-" ?@))
              ((save-excursion
                 (goto-char (map-elt bounds :start))
                 (eq (char-before) ?@)))
              (subagents agent-shell-opencode--subagents)
              (descriptions (mapcar (lambda (a)
                                      (cons (map-elt a :name)
                                            (map-elt a :description)))
                                    subagents)))
    (list (map-elt bounds :start) (map-elt bounds :end)
          (mapcar #'car descriptions)
          :exclusive t
          :annotation-function
          (lambda (name)
            (when-let* ((desc (map-elt descriptions name)))
              (concat "  " desc)))
          :company-kind (lambda (_) 'function))))

(defun agent-shell-opencode--setup-completion ()
  "Set up @@subagent completion in agent-shell."
  (add-hook 'completion-at-point-functions
            #'agent-shell-opencode--subagent-completion-at-point
            nil t))

(add-hook 'agent-shell-mode-hook #'agent-shell-opencode--setup-completion)

Activity

  1. added
    discussionUsed for feature requests, proposals, ideas, etc. Open discussion
    on Feb 1, 2026
  2. github-actions commented on Feb 1, 2026

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

  3. YichenLi00 commented on Feb 5, 2026

    @YichenLi00

    The filter is at packages/opencode/src/acp/agent.ts:976:

    return agents
      .filter((agent) => agent.mode !== "subagent" && !agent.hidden)

    Could return all non-hidden agents and include the mode field, letting clients decide what to show. This keeps backward compatibility since the data shape stays the same, just with more entries.

    @rekram1-node I noticed you've worked on this file before. Would you be open to a PR for this?

  4. VandeeFeng commented on Feb 5, 2026

    @VandeeFeng
    Author

    The filter is at packages/opencode/src/acp/agent.ts:976:

    return agents
    .filter((agent) => agent.mode !== "subagent" && !agent.hidden)

    Could return all non-hidden agents and include the mode field, letting clients decide what to show. This keeps backward compatibility since the data shape stays the same, just with more entries.可以返回所有非隐藏代理并包含 mode 字段,让客户端决定显示什么。这保持了向后兼容性,因为数据形状保持不变,只是条目更多。

    @rekram1-node I noticed you've worked on this file before. Would you be open to a PR for this?

    Yes ! That is the code which I exactly want to mention!

  5. YichenLi00 commented on Feb 7, 2026

    @YichenLi00

    PR #12563 implements this. Added optional mode field to ModeOption type and removed the server-side subagent filter. Clients can now access subagent info for @mentions and filter by mode if needed. Backward compatible - existing clients continue to work.

  6. github-actions commented on Apr 9, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 90 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

acpdiscussionUsed for feature requests, proposals, ideas, etc. Open discussion

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions