Hello,

Org-Babel lets you name a source block and call it like a function, and it
serializes values across languages.  What it does not offer is a convenient,
documented way to do that *from Lisp*: to call a named block, pass it live
values, and get a typed result back.

The pieces exist, but none of them is quite a front door:

  - ":var out=BLOCK(arg=val)" / "#+call:" are the documented layer, but the
    argument is fixed in the header -- you cannot forward a value that only
    exists at call time, nor call a block from within another block's body.

  - org-sbe is documented for table formulas only.  It returns strings and
    splices its arguments in as literal text rather than evaluating them, so a
    bound Lisp variable becomes a reference lookup, not a value.

  - org-babel-ref-resolve does orchestrate and preserves types, but it is an
    internal function, and you assemble the call as a string -- which forces a
    quoting rule: a scalar interpolates with %S, but a list or table read back
    from a reference string is evaluated as code and has to be quoted ('%S).

The attached patch adds a small, documented entry point, org-babel-call:

    (org-babel-call "summary" :run 1 :rows table)

It binds variables from a plist of real Lisp values and returns the block's
result with its type intact, so calls compose and nest as ordinary Lisp.

Design notes:

  - It is built on the public org-babel-execute-src-block.  When a :var
    parameter is supplied as a (NAME . VALUE) cons rather than a "NAME=VALUE"
    string, org-babel-process-params uses the value verbatim -- no
    org-babel-read, no eval, no quoting.  So any value, including a whole
    table, is forwarded as an object.

  - The block is located with org-babel-find-named-block rather than
    org-babel-goto-named-src-block.  The latter is an interactive command that
    pushes the mark ring and calls org-fold-show-context (which unfolds the
    target); save-excursion undoes neither, so it is unsuitable for a silent
    helper.

Open questions for the list:

  - Name and home: org-babel-call in ob-core.el -- acceptable?
  - Should it accept extra header-argument overrides (e.g. a :results value),
    or is the fixed ":results silent" (return the value, no buffer insertion)
    right for a first version?
  - Keyword keys (:run) versus plain symbols -- any preference?

If the interface is agreeable, a manual section under "Evaluating Code Blocks"
should follow.

A note on provenance, so you can weigh the implementation accordingly: the code
was written by an AI coding assistant -- I supplied the use case, did the review
and testing, and decided it was worth proposing -- and I do not have FSF
copyright assignment on file.  Please treat it as an illustration of the 
proposed
interface rather than a copyright-assignable contribution: what I am bringing is
the use case and the proposal that Org would benefit from such a function.  I
would be glad to see it reimplemented independently by someone with assignment,
or to keep this to a discussion of whether the interface is wanted.

For reference, the change is attached as a patch and is on a branch here:
  https://github.com/dbausch/org-mode/tree/org-babel-call

Thanks,
Daniel
From 7c97d7bb939e9209b362681e714b28f7820748ab Mon Sep 17 00:00:00 2001
From: Daniel Bausch <[email protected]>
Date: Tue, 30 Jun 2026 01:48:17 +0200
Subject: [PATCH] ob-core: Add org-babel-call

* lisp/ob-core.el (org-babel-call): New function.  Call a named source
block from Lisp, binding variables from a property list of real Lisp
values and returning the typed result.  Built on
`org-babel-execute-src-block' with cons-valued `:var' parameters so the
values are forwarded as objects rather than serialized into a string,
and located with `org-babel-find-named-block' to avoid the buffer
side effects of `org-babel-goto-named-src-block'.
* etc/ORG-NEWS (New functions and changes in function arguments):
Announce it.
---
 etc/ORG-NEWS    | 15 +++++++++++++++
 lisp/ob-core.el | 29 +++++++++++++++++++++++++++++
 2 files changed, 44 insertions(+)

diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS
index 261d5d3..a5dfa8a 100644
--- a/etc/ORG-NEWS
+++ b/etc/ORG-NEWS
@@ -228,6 +228,21 @@ needs. The recommended alternative value is ~itemize~.
 Given the completed and total number of tasks, format the percent
 cookie =[N%]=.
 
+*** New function ~org-babel-call~
+
+Call a named source block from Lisp and return its result, binding
+variables from a property list of real Lisp values:
+
+#+begin_src emacs-lisp
+(org-babel-call "summary" :run 1 :rows table)
+#+end_src
+
+Unlike a header reference such as =:var out=block(arg=val)=, the
+arguments are ordinary Lisp objects rather than a string, so lists and
+tables are forwarded without quoting.  The block runs with its own
+header arguments; only the listed variables are overridden.  The result
+keeps its type, so calls compose and nest.
+
 ** Removed or renamed functions and variables
 
 *** ~org-babel-remote-temporary-directory~ is now obsolete
diff --git a/lisp/ob-core.el b/lisp/ob-core.el
index 7f28301..860c688 100644
--- a/lisp/ob-core.el
+++ b/lisp/ob-core.el
@@ -982,6 +982,35 @@ guess will be made."
 	    (run-hooks 'org-babel-after-execute-hook)
 	    result)))))))
 
+(defun org-babel-call (name &rest bindings)
+  "Call the named source block NAME and return its result.
+
+NAME is the name of a source block in the current buffer.  BINDINGS is
+a property list of variable assignments; each key is a keyword naming
+one of the block's variables and the following element is the Lisp
+value to bind to it.  For example:
+
+  (org-babel-call \"summary\" :run 1 :rows table)
+
+The values are passed as real Lisp objects, so any value -- a number,
+string, list or whole table -- is forwarded without serialization or
+quoting (contrast a header reference like :var out=NAME(arg=val), whose
+arguments are read back from a string).  NAME runs with its own header
+arguments (language, session, and so on); only the listed variables are
+overridden.  The result keeps its type and is not inserted into the
+buffer, so calls compose and nest as ordinary Lisp."
+  (let ((location (or (org-babel-find-named-block name)
+                      (user-error "No source block named `%s'" name))))
+    (org-babel-execute-src-block
+     nil
+     (org-with-point-at location (org-babel-get-src-block-info 'no-eval))
+     (append
+      (cl-loop for (key value) on bindings by #'cddr
+               collect (cons :var
+                             (cons (intern (substring (symbol-name key) 1))
+                                   value)))
+      '((:results . "silent"))))))
+
 (defun org-babel-expand-body:generic (body params &optional var-lines)
   "Expand BODY with PARAMS.
 Expand a block of code with org-babel according to its header
-- 
2.54.0

Reply via email to